Tracking Issue for auto traits (auto_traits) -- formerly called opt-in built-in traits (optin_builtin_traits)

83bf2bf
Opened by Flavio Percoco at 2024-12-21 05:03:16

Checklist

Here is a check-list of code to write and tricky scenarios to be sure we handle:

  • [ ] forbid conditional negative impls as described here
  • [ ] conditional negative impls not properly enforced https://github.com/rust-lang/rust/issues/23072
  • [ ] impl !Pod for .. should not be legal https://github.com/rust-lang/rust/issues/28475
  • [x] fix feature-gate for impl Foo for .. https://github.com/rust-lang/rust/issues/23225
  • [x] defaulted traits ought to be have more restrictive coherence rules https://github.com/rust-lang/rust/issues/22978
  • [x] defaulted traits should have no methods #23080 / https://github.com/rust-lang/rust/pull/23117
  • [x] Add parser support for impl Foo for ..
  • [x] Port Send/Sync to use new infrastructure internally
  • [x] Add unsafe impl Send for .. / unsafe impl Sync for ..
  • [x] Object types should never match against a defaulted trait (though the object type itself may add a candidate for traits that appear in the object type)
  • [x] When a defaulted trait matches, it should impose any supertrait bounds on the matched type as nested obligations
  • [x] Be wary of type inference -- if we haven't resolved a type yet, we have to be ambiguous
  • [ ] Systematic testing for constituent_types
  • [x] Coherence interaction: a defaulted trait can only be implemented for structs/enums
  • [x] Coherence interaction: a trait can only be defaulted in the crate where it is defined
  • [x] Coherence interaction: a trait can only be defaulted once
  • [x] Defaulted impls cannot be generic
  • [x] Fix the interaction with PhantomData. OIBIT should treat PhantomData<T> as if there were an instance of T reachable rather than breaking it down like it would a different struct. https://github.com/rust-lang/rust/pull/23091
  • [x] Allow negative implementations for traits that have a default implementation (besides Send/Sync).

Original text

This is a tracking issue for the approved RFC opt-in-builtin-traits

Nominating

  1. This is the tracking issue for RFC 19.

    Checklist

    Here is a check-list of code to write and tricky scenarios to be sure we handle:

    • [ ] forbid conditional negative impls as described here
    • [x] conditional negative impls not properly enforced https://github.com/rust-lang/rust/issues/23072
    • [x] impl !Pod for .. should not be legal https://github.com/rust-lang/rust/issues/28475
    • [x] fix feature-gate for impl Foo for .. https://github.com/rust-lang/rust/issues/23225
    • [x] defaulted traits ought to be have more restrictive coherence rules https://github.com/rust-lang/rust/issues/22978
    • [x] defaulted traits should have no methods #23080 / https://github.com/rust-lang/rust/pull/23117
    • [x] Add parser support for impl Foo for ..
    • [x] Port Send/Sync to use new infrastructure internally
    • [x] Add unsafe impl Send for .. / unsafe impl Sync for ..
    • [x] Object types should never match against a defaulted trait (though the object type itself may add a candidate for traits that appear in the object type)
    • [x] When a defaulted trait matches, it should impose any supertrait bounds on the matched type as nested obligations
    • [x] Be wary of type inference -- if we haven't resolved a type yet, we have to be ambiguous
    • [ ] Systematic testing for constituent_types
    • [x] Coherence interaction: a defaulted trait can only be implemented for structs/enums
    • [x] Coherence interaction: a trait can only be defaulted in the crate where it is defined
    • [x] Coherence interaction: a trait can only be defaulted once
    • [x] Coherence interaction: an auto-trait can be redundantly implemented for an object that has it - #56934
    • [x] Defaulted impls cannot be generic
    • [x] Fix the interaction with PhantomData. OIBIT should treat PhantomData<T> as if there were an instance of T reachable rather than breaking it down like it would a different struct. https://github.com/rust-lang/rust/pull/23091
    • [x] Allow negative implementations for traits that have a default implementation (besides Send/Sync).
    • [x] https://github.com/rust-lang/rust/issues/104808 lifetimes on auto traits are buggy
    • [x] Coherence rules: is impl AutoTrait for dyn Trait legal? https://github.com/rust-lang/rust/issues/13231#issuecomment-1397480267
    • [x] Interaction with dyn safety. https://github.com/rust-lang/rust/pull/107082
    • [x] Do [u8] negative impls affect str. https://github.com/rust-lang/rust/issues/13231#issuecomment-1399386472

    XAMPPRocky at 2018-07-13 23:36:07

  2. Marking 1.0, P-backcompat-lang.

    Felix S Klock II at 2014-04-03 20:21:57

  3. cc me

    Niko Matsakis at 2014-04-15 13:56:44

  4. I'm actively working on this. Unfortunately, I've moved quite slowly because of other bugs I had to fix. I hope to be able to submit a PR this week.

    Flavio Percoco at 2014-04-22 23:29:37

  5. Per https://github.com/rust-lang/rfcs/pull/127

    • require implementation of Copy
    • require implementation of Send/Share for a newtyped wrapper around *T or *mut T, otherwise *T/*mut T are not Send/Share

    Patrick Walton at 2014-09-15 21:30:16

  6. Nominating for removal from milestone; I believe the backcompat issues have been handled.

    Aaron Turon at 2015-01-08 19:46:39

  7. removing from 1.0 milestone.

    Felix S Klock II at 2015-01-08 21:33:48

  8. I believe this has basically all been implemented to the fullest extent it will for now, so closing.

    Alex Crichton at 2016-02-18 18:51:29

  9. For posterity's sake, what hasn't been?

    One question I have about a lot of these older RFCs is how much I can actually rely on their text for documenting semantics. For really old stuff like this, I worry that the RFCs might not be useful for people. This might just be the reality, though.

    On Feb 18, 2016, 13:51 -0500, Alex Crichtonnotifications@github.com, wrote:

    I believe this has basically all been implemented to the fullest extent it will for now, so closing.

    — Reply to this email directly orview it on GitHub(https://github.com/rust-lang/rust/issues/13231#issuecomment-185857012).

    Steve Klabnik at 2016-02-18 19:36:44

  10. There's definitely more work to do here (see the checklist -- there may be more), and this should ultimately become the tracker for stabilization.

    cc @nikomatsakis

    Aaron Turon at 2016-02-18 19:41:27

  11. In this particular case, the RFC is definitely out of sync with the current implementation. One of my pending worklist items has been to write an update to the text.

    @steveklabnik I also think that, as part of the stabilization process, we should go back and update the RFCs to bring them inline with the final results.

    On Thu, Feb 18, 2016 at 2:42 PM, Aaron Turon notifications@github.com wrote:

    There's definitely more work to do here (see the checklist -- there may be more), and this should ultimately become the tracker for stabilization.

    cc @nikomatsakis https://github.com/nikomatsakis

    — Reply to this email directly or view it on GitHub https://github.com/rust-lang/rust/issues/13231#issuecomment-185885630.

    Niko Matsakis at 2016-02-20 17:28:53

  12. I'm writing a library that depends on OIBIT. In particular, it depends on the following coding pattern working:

    trait NotSame {}
    impl NotSame for .. {}
    impl<T> !NotSame for (T, T) {}
    
    trait Foo<A> {}
    
    struct Bar<B>(B);
    
    impl<A, B> Foo<A> for Bar<B> where (A, B): NotSame {
        // stuff
    }
    
    impl<A> Foo<A> for Bar<A> {
        // other stuff
    }
    

    This works on nightly at the moment. Is this relying on any behaviour that is disallowed by the intended functionality? I.e. will this break by the time this is stabilised? Thanks.

    Dylan Ede at 2016-02-22 18:16:16

  13. RFC needs to be ammended, not sure for what.

    Brian Anderson at 2016-08-25 16:54:16

  14. Roughly speaking I wanted to formalize the following things:

    • [ ] that we no longer do fallback with priorities, but instead do "default impls" more like what is described here https://gist.github.com/nikomatsakis/d5fe4a4b5b075b3d0ec9
    • [ ] coinductive matching semantics (loosely described in the RFC today)
    • [ ] the name auto trait
    • [ ] perhaps a change in the syntax

    The first two are mostly reflecting the current semantics, though.

    Niko Matsakis at 2016-08-25 16:56:54

  15. @dylanede hmm, sorry I failed to answer your question before. I'm sorry to say this, but your code snippet is not expected to work.

    In particular, the way to think about it is that, by adding a negative impl for (T, T), you have suppressed the default impl that would have been provided -- so now there is no positive impl! Therefore, (X, Y): NotSame cannot hold for any tuple. (But, (X, Y): !NotSame, if we ever added negative impls, would only hold if X == Y; otherwise, (X, Y) neither implements NotSame nor !NotSame, a situation which @withoutboats artfully described as (X, Y): ?NotSame.)

    Note that the behavior for your example is inconsistent already. For example, while your code compiles, this similar snippet does not.

    I had to dig a bit into the code to see why your example worked at all: the answer is that the "first pass" test which tries to unify with various impls fails to unify for types like u32 and i32 -- it will basically succeed "modulo regions". So then later when we check to see if there were any negative impls that applied, we find out that there were not (in the case of u32, i32) but that there are (in the case of &'static u32, &'a u32). If we were properly implementing the RFC the way that I had intended to amend it, we would always find applicable negative impls (in fact, until such time as we add general negative reasoning, I had intended to make the negative impl you wrote illegal -- you would have to write negative impls that are "fully general", much like the rules we enforce for Drop.)

    Niko Matsakis at 2016-08-26 14:04:24

  16. This is… beyond me. Someday maybe. 😬

    But I'm trying to document all the things, so: can someone take a gander at the reference and the book and give me a reasonable summary of the extent to which this is or isn't documented?

    Chris Krycho at 2016-12-30 18:56:47

  17. I just wanted to note that if we add immovable types in it's current form, default traits would have to be declared with trait Send: ?Move {} to avoid having Move as a supertrait (which has soundness bugs).

    Zoxc at 2017-03-10 18:11:36

  18. Resuming the bikeshed which I had previously missed, not quite sold on the new auto trait syntax as of https://github.com/rust-lang/rust/pull/45247 (though nevertheless an improvement over the old). Not only is it a new keyword (a heavyweight addition) for a feature that is intended to be incredibly obscure, but it risks confusion for people coming from C++.

    In https://github.com/rust-lang/rust/pull/45247 an alternative of #[autoderive] was floated and seemed to have quite some support. I think it makes sense as a general policy for us to prefer attributes/macros for obscure features, and later offer keywords/operators if anyone demands (which was the trajectory for ?, and the possible trajectory for async/await, and unlikely to ever occur for autotraits IMO).

    bstrie at 2017-11-07 23:48:27

  19. Random thing I noticed is that OIBIT (hackily) allows checking for type equality (and hence type inequality):

    // sealed for good measure
    mod private {
        pub auto trait NotSame {}
        impl<T> !NotSame for (T, T) {}
    }
    use NotSame;
    
    trait ReqDifferent<T> where (T, Self): NotSame {}
    trait ReqSame<T> where (T, Self): !NotSame {}
    

    I assume that this is not desired.

    Clar Fon at 2018-05-27 20:44:13

  20. No, it's not. This behaviour is a bug, See the responses to https://github.com/rust-lang/rust/issues/13231#issuecomment-187302270

    Dylan Ede at 2018-05-27 20:54:40

  21. Wow, I completely missed that comment. Appears that this has already been discussed

    Clar Fon at 2018-05-27 21:03:50

  22. I ran into this issue when trying to implement !Sync for a type of mine. I know you can work around this with PhantomData well but it makes for ugly error messages. I wish I could tell my users that my type is not Sync and not that *mut () or something similar contained in my type is not sync.

    Armin Ronacher at 2018-06-14 21:22:05

  23. Added #56934

    Ariel Ben-Yehuda at 2018-12-17 23:02:31

  24. With the current implementation (that might be buggy) one can solve some simple specialization cases with this feature which is nice: https://gist.github.com/rust-play/4ea94f766d49abdbc9b2c55e49a9d2d9

    Robin Freyler at 2019-05-09 08:22:54

  25. @clarfon note that type equality (but sadly not inequality) can be reached with simple traits on stable:

    trait Id {
        type This: ?Sized;
    }
    
    impl<T: ?Sized> Id for T {
        type This = T;
    }
    
    fn assert_same<A, B>() where A: Id<This = B> {}
    

    (playground)

    waffle at 2020-07-07 08:26:34

  26. The following code (playground link) is rejected by the compiler because Something is not implemented for the type dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static. Providing an explicit impl makes the code compile. Is this a bug or intended behavior?

    #![feature(optin_builtin_traits)]
    
    pub auto trait Something {}
    
    // Uncomment this to make the code compile
    //
    //impl Something for dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static {}
    
    pub fn foo<T: Something>(_: T) {}
    
    #[test]
    fn test() {
        use std::io::*;
        foo(Error::new(ErrorKind::Other, ""));
    }
    

    Johannes Dahlström at 2020-08-03 13:50:01

  27. That is the correct behavior @jdahlstrom. std::io::Error does not get a Something impl because it holds something that does not necessarily implement Something. In general you would only get the auto trait impl if all the fields are known to implement that auto trait.

    For example:

    struct S;
    impl std::error::Error for S {}
    impl std::fmt::Display for S {fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {Ok(())}}
    impl !Something for S {}
    foo(Error::new(ErrorKind::Other, S));  // io::Error containing an S.
    

    David Tolnay at 2020-08-04 04:50:18

  28. @dtolnay Ah, of course. Thanks.

    Johannes Dahlström at 2020-08-04 10:51:44

  29. I updated the name of this issue to be consistent with the new feature name (see #79336).

    Noah Lev at 2020-11-29 22:55:16

  30. The RFC doc talks about the Share trait, but I think that is now renamed to Sync? (Or is it / was it different?) Could I suggest the RFC text be updated, at least with a note if not a search-and-replace?

    Martin Pool at 2020-12-02 18:06:09

  31. @sourcefrog Usually RFCs aren't updated since there are so many and it would be too much work to keep them up-to-date. But you can open a PR on rust-lang/rfcs and see if indeed it was renamed and they think it's a good idea to update the RFC.

    Noah Lev at 2020-12-09 05:19:55

  32. #107082 proposes an adjustment to the coherence rules involving autotraits.

    David Tolnay at 2023-01-19 19:14:13

  33. I am adding a note to the checklist that the interaction between auto traits and str needs to be revisited as part of any eventual stabilization.

    Currently with the following code:

    auto trait AutoTrait {}
    
    impl<T> !AutoTrait for [T] {}
    

    we get str implements AutoTrait but [u8] does not.

    Under a different libcore-based str library implementation something like https://github.com/rust-lang/rust/pull/70911, we'd get that neither implements AutoTrait.

    If we want to ever be able to push something like https://github.com/rust-lang/rust/pull/70911 through in the future, there may need to be str-specific special cases in the autotrait feature for now.

    David Tolnay at 2023-01-22 02:14:07

  34. This RFC is a little dated and concerns multiple topics ("auto trait", other automatically-implemented marker traits, unsafe traits). Lets summarise the RFC:

    Motivation

    To paraphrase (with comments):

    1. Send and Share (is this the old Sync?) should not be opt-in. This issue is solved.
    2. Move Send and Share out of the language into the standard library.
    3. Allow user-defined classification traits. Suggested uses are: "tainted", Snapshot, NoManaged, NoDrop (now !Drop).

    Detailed design

    Default and negative impls

    We add a notion of a default impl, written:

    impl Trait for .. { }
    

    (This is now just auto trait Trait {} IIUC.)

    Negative impls are only permitted if Trait has a default impl.

    This is reliant on having a specified "default impl", as above (not just impl<T> Trait for T {} and not the type of default impl used for specialization). If an auto-trait is defined as exactly "a trait which is implemented by default, with opt-out", then auto trait Trait {} suffices.

    Intuitively, to check whether a trait Foo that contains a default impl is implemented for some type T, we first check for explicit (positive) impls that apply to T. If any are found, then T implements Foo. Otherwise, we check for negative impls. If any are found, then T does not implement Foo. If neither positive nor negative impls were found, we proceed to check the component types of T (i.e., the types of a struct's fields) to determine whether all of them implement Foo. If so, then Foo is considered implemented by T.

    Aha, the (informal) definition of an auto trait.

    Modeling Send and Share using default traits

    The RFC details how to define Send and Share as auto traits (the current Sync is slightly different, but still an auto trait).

    (The private trait Freeze is another auto trait with some explicit opt-in and opt-out impls.)

    (auto trait Unpin is another public auto trait with some explicit opt-ins. As a work-around for not having stable negative impls, Unpin has a negative impl for public type PhantomPinned.)

    The Copy and Sized traits

    Copy is just an ordinary (opt-in) marker trait.

    Sized looks like an ordinary trait but the compiler provides all impls (no opt-in or opt-out possible). (The same is true for trait Unsize<T: ?Sized>.)


    My thoughts follow.

    "auto trait" in conjunction with negative impls are a niche solution to moving a few trait definitions from the language to the standard library:

    • Send (user-defined opt-in and opt-out not allowed)
    • Sync (user-defined opt-in and opt-out not allowed)
    • Freeze (not public)
    • Unpin (user-defined opt-out allowed via an alternative mechanism)

    "auto trait" does not allow all "automatically defined traits" to be offloaded from the compiler: e.g. Sized, Unsize<T>, Drop.

    The RFC mentions a few other uses of auto traits:

    • "tainted" data to be excluded from some generic routine (I do not understand the motivation)
    • Snapshot, a marker such that a type T: Clone + Snapshot indicates that a clone preserves value (i.e. that the clone may not be mutated by some internal reference); this is documentation-via-type-system and possibly has some utility though I don't see how such a bound would be required (the preferred method of preserving generic data will often be to serialize, which allows writing to an external medium)
    • NoManaged
    • NoDrop. This is !Drop (also known as linear types) and explored elsewhere

    The only other possible usage I can think of:

    • bytemuck::Pod: but this does not work since Pod types must not have padding

    Hence it remains unclear whether there is sufficient utility for "auto traits".

    The other angle for auto-traits is as an opt-out marker trait (i.e. negative impls #68318). These do not require any rules pertaining to component types, thus, for simplicity, should not use "auto traits" as defined by RFC 19.

    My hesitant recommendation

    Do not make "auto traits" a public feature and close this issue. They have recognised utility within the standard library but likely very little utility otherwise. (Perhaps in the future a more general form of blanket impls able to emulate the logic of auto traits will be possible. In the mean-time, I simply don't think auto traits have sufficient utility to become a fully fledged language feature.)

    Possibly rename the existing "auto traits" to allow the term to be used for a simple "opt-out" trait for use with negative implementations (but this would be the topic of negative impls or a whole new RFC).

    Diggory Hardy at 2023-06-30 12:18:09

  35. To me, it mostly has value for checking whether the types involved in a closure satisfy a specific property. A perfect example of this, outside the usual Send/Sync, is UnwindSafe (however badly named that is).

    Concretely, I'm using it (behind a feature flag) to make autorelease pools in Objective-C sound, just as a data point for how it's useful.

    Mads Marquart at 2023-06-30 15:33:44

  36. Thanks for the examples, adding motivation for this feature.

    The aspects of this feature I'm least comfortable with are:

    • Using a simple name like "auto trait" for what is a complex feature ("auto recursive trait"?)
    • The idea of using it as a key building block of negative impls. Potentially that feature should introduce another form of opt-out trait. (Possibly not even in the base RFC; there's some utility in negative impls without negative reasoning.)

    Diggory Hardy at 2023-06-30 16:58:06

  37. My best recollection of current lang consensus is that S-tracking-perma-unstable is correct here. This is essential for the standard library, but the ecosystem effects of "you need to know to opt out of dozens of random auto traits created by various crates" seems unworkable for a crate ecosystem, so without some solution to that, these likely will remain a standard library exclusive privilege.

    scottmcm at 2023-09-21 18:53:39

  38. how about coupling the auto traits so that they only get auto-used when the crate defining them is a direct dependency of the current crate (so no automatic spill of auto traits happens)? (making it crate-level opt-in, trait-level opt-out instead of pure opt-out)

    Ellen Emilia Anna Zscheile at 2023-09-22 14:17:06

  39. I think pyo3 usecase wasn’t mentioned here, so I’ll bring it as a data point.

    pyo3 needs to track which types can only be accessed when Python’s GIL is held. Currently it uses Send as a proxy for this property, but it’s neither neccessary nor sufficient requirement and only works as a rough heuristic. It seems like there’s no good way to do it properly without auto traits (+ negative impls). Similar usecase was mentioned above with ObjC, so it seems like this feature is neccessary for sound FFI at least in some cases.

    I think that this is an evidence that auto traits currently have usecases outside of standard library. Maybe these usecases could be covered by another feature, maybe they’re not enough to sway the decision, but I think it should be recognized that auto traits have utility outside of standard library, and making them perma-unstable will probably make some usecases perma-unsound, unless some other solution is found. I feel like if this issue is to be closed, this tradeoff (“we’re OK with these cases having no known sound solution”) should be explicitly made in the final decision.

    https://docs.rs/pyo3/latest/pyo3/marker/trait.Ungil.html — trait in pyo3 that should be auto https://github.com/PyO3/pyo3/issues/2141 — issue in the pyo3 repo

    Max “Goldstein” Siling at 2023-09-27 15:04:21

  40. I’d also like to address this point specifically wrt Ungil:

    you need to know to opt out of dozens of random auto traits created by various crates

    Opting out of Ungil would only be sensible when you’re doing something with Python and are already using pyo3. In my intuition, auto traits are used to represent some properties related to a certain system (e.g. Send and Sync represent properties related to threads). You opt out of them when your type has some special properties in this system (e.g. is safe to access from multiple threads). Opting out of an auto trait is thus only sensible if you’re actually using this system, so you can promise to uphold some invariants. Opting out of auto traits related to systems you’re not actively integrating with feels like a wrong thing to do in almost any situation.

    Max “Goldstein” Siling at 2023-09-27 16:01:08

  41. Opting out of auto traits related to systems you’re not actively integrating with feels like a wrong thing to do in almost any situation.

    In my case too, you'd only need to opt out of AutoreleaseSafe when you're doing stuff with Objective-C (or perhaps Swift, but they're close enough) autorelease pools.

    I'll add that if a library wanted to make sure to opt out of all auto traits (e.g. if it implemented some sort of dynamic type), even for generic structs, they could do:

    trait HelperTrait {}
    
    struct MyTypeThatOptsOutOfEverything<T> {
        inner: T,
        p: PhantomData<dyn HelperTrait>,
    }
    

    Of course this is yet another SemVer footgun to be aware of, but fortunately we have tools for that now.

    Mads Marquart at 2023-09-27 16:43:02

  42. Oops, I mixed up “opting out” and “opting in” in my original message. Either way, the point stands: I don’t see a reason to opt out of arbitrary auto traits unless you’re doing something that can affect this particular system. Author of the auto trait guarantees that it’s safe to impl it if all the fields impl it.

    Max “Goldstein” Siling at 2023-09-27 16:52:07

  43. For Ungil, I’ve found the issue

    • https://github.com/PyO3/pyo3/issues/3640

    which is that an auto-trait is unable to restrict access to certain types even if those types always feature a non-'static lifetime as provided by to a higher-kinded for<'a> FnOnce(SomeType<'a>)-style callback.

    I’m relatively certain that the AutoreleaseSafe example suffers from the same issue.

    Of course one approach would be to declare scoped-tls unsound on the (arguably quite thin) grounds that the standard library only sets precedent that 'static data is allowed to be shared via thread-local storage. Assuming we don’t declare scoped-tls unsound, an auto traits feature seems entirely unable to be a solution for sound API in either of the API cases for AutoreleaseSafe nor Ungil.

    Frank Steffahn at 2023-12-11 14:04:09

  44. Oh, that’s really sad. If scoped-tls is valid (and I don’t see a reason for it not to be), then auto trait Ungil can only make pyo3 less-obviously-unsound instead of sound. IMHO that’s still a valid reason for it to be an auto trait, but it’s really unfortunate that it doesn’t just make it clean and sound.

    Max “Goldstein” Siling at 2023-12-11 14:40:08

  45. Hmm, I don't think that's a problem for objc2's AutoreleaseSafe, since the only type it's used on is AutoreleasePool<'pool>, whose only purpose is to carry a lifetime (it's a ZST that doesn't actually carry any data), which it seems like scoped-tls-hkt manages to properly bind to the execution of the closure.

    So even though we can access the pool, we can't actually do anything bad with it, since it's only the lifetime that we care about, and that is still correctly bounded. See the following example for details.

    ```cargo
    [dependencies]
    # requires `auto_traits` feature
    objc2 = { version = "0.5", features = ["unstable-autoreleasesafe"] }
    scoped-tls-hkt = "0.1"
    ```
    
    use objc2::rc::{autoreleasepool, AutoreleasePool, Id};
    use objc2::runtime::NSObject;
    use scoped_tls_hkt::scoped_thread_local;
    
    fn main() {
        autoreleasepool(|pool1| {
            scoped_thread_local!(static POOL: for<'pool> AutoreleasePool<'pool>);
    
            POOL.set(pool1, || {
                let obj = autoreleasepool(|_pool2| {
                    POOL.with(|pool1| {
                        Id::autorelease(NSObject::new(), pool1)
                    })
                });
    
                // If we manage to get here, then that's unsound, since the object
                // would have been released in the second autorelease pool.
    
                println!("{obj:?}");
            });
        });
    }
    

    If you manage to produce a similar example that does compile, then I'd love to know!

    Mads Marquart at 2023-12-11 15:31:47

  46. @madsmtm Here we go:

    use std::cell::Cell;
    
    use objc2::rc::{autoreleasepool, AutoreleasePool, Id};
    use objc2::runtime::NSObject;
    use scoped_tls_hkt::scoped_thread_local;
    
    struct PoolHolder<'p> {
        pool1: AutoreleasePool<'p>,
        obj_cell: &'p Cell<Option<&'p NSObject>>,
    }
    trait IPoolHolder {
        fn autorelease(&self, id: Id<NSObject>);
    }
    impl<'pool> IPoolHolder for PoolHolder<'pool> {
        fn autorelease(&self, id: Id<NSObject>) {
            self.obj_cell.set(Some(Id::autorelease(id, self.pool1)));
        }
    }
    
    fn main() {
        autoreleasepool(|pool1| {
            scoped_thread_local!(static POOL_HOLDER: for<'p> &'p dyn IPoolHolder);
    
            let obj_cell = &Cell::new(None);
    
            POOL_HOLDER.set(&PoolHolder { pool1, obj_cell }, || {
                autoreleasepool(|_pool2| {
                    POOL_HOLDER.with(|pool1_holder| pool1_holder.autorelease(NSObject::new()))
                });
    
                let obj: &NSObject = obj_cell.get().unwrap();
    
                // If we manage to get here, then that's unsound, since the object
                // would have been released in the second autorelease pool.
    
                println!("{obj:?}");
            });
        });
    }
    

    Frank Steffahn at 2023-12-11 16:27:00