Open flaper87 opened 10 years ago
Marking 1.0, P-backcompat-lang.
cc me
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.
Per https://github.com/rust-lang/rfcs/pull/127
*T
or *mut T
, otherwise *T
/*mut T
are not Send/ShareNominating for removal from milestone; I believe the backcompat issues have been handled.
removing from 1.0 milestone.
I believe this has basically all been implemented to the fullest extent it will for now, so closing.
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).
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
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.
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.
RFC needs to be ammended, not sure for what.
Roughly speaking I wanted to formalize the following things:
auto trait
The first two are mostly reflecting the current semantics, though.
@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
.)
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?
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).
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).
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.
No, it's not. This behaviour is a bug, See the responses to https://github.com/rust-lang/rust/issues/13231#issuecomment-187302270
Wow, I completely missed that comment. Appears that this has already been discussed
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.
Added #56934
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
@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> {}
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, ""));
}
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.
@dtolnay Ah, of course. Thanks.
I updated the name of this issue to be consistent with the new feature name (see #79336).
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?
@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.
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.
This RFC is a little dated and concerns multiple topics ("auto trait", other automatically-implemented marker traits, unsafe
traits). Lets summarise the RFC:
To paraphrase (with comments):
Send
and Share
(is this the old Sync
?) should not be opt-in. This issue is solved.Send
and Share
out of the language into the standard library.Snapshot
, NoManaged
, NoDrop
(now !Drop
).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 typeT
, we first check for explicit (positive) impls that apply toT
. If any are found, thenT
implementsFoo
. Otherwise, we check for negative impls. If any are found, thenT
does not implementFoo
. If neither positive nor negative impls were found, we proceed to check the component types ofT
(i.e., the types of a struct's fields) to determine whether all of them implementFoo
. If so, thenFoo
is considered implemented byT
.
Aha, the (informal) definition of an auto trait
.
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
.)
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:
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 elsewhereThe only other possible usage I can think of:
bytemuck::Pod
: but this does not work since Pod
types must not have paddingHence 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.
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).
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.
Thanks for the examples, adding motivation for this feature.
The aspects of this feature I'm least comfortable with are:
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.
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)
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
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.
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.
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.
For Ungil
, I’ve found the issue
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
.
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.
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!
@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:?}");
});
});
}
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:
impl !Pod for ..
should not be legal https://github.com/rust-lang/rust/issues/28475impl Foo for ..
https://github.com/rust-lang/rust/issues/23225impl Foo for ..
Send/Sync
to use new infrastructure internallyunsafe impl Send for ..
/unsafe impl Sync for ..
constituent_types
impl AutoTrait for dyn Trait
legal? https://github.com/rust-lang/rust/issues/13231#issuecomment-1397480267[u8]
negative impls affectstr
. https://github.com/rust-lang/rust/issues/13231#issuecomment-1399386472