-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Improve the Send trait. #458
Conversation
Hmm, this sounds very nice! What does your |
@Ericson2314 It would be superficially similar to @nikomatsakis's existing rayon library, but it would likely use a Send bound instead of a Sync bound (for the reasons outlined in the RFC). There are some subtleties around task failure but they can be resolved with clever Drop trait implementations (I believe Servo does this currently). |
Cool, I did not know about that library. So under your proposal, |
I'm not sure any code in the standard library is safe to use as is in a fork join context, but possibly. The RFC is more about making sure Rust the language supports such libraries properly in the future. |
Gotcha. Sounds good either way. |
A user in IRC just ran into a situation where he or she wasn't able to box a type with a reference in it as an This is not something I mentioned in the issue, since I think it's a side benefit, but in general I think this provides some evidence that it's useful to be able to specify |
|
||
Another important note is that with this definition, ```Send``` will fulfill the proposed role of ```Sync``` in a fork-join concurrency library. At present, to use ```Sync``` in a fork-join library one must make the implicit assumption that if ```T``` is ```Sync```, ```T``` is ```Send```. One might be tempted to codify this by making ```Sync``` a subtype of ```Send```. Unfortunately, this is not always the case, though it should be most of the time. A type can be created with ```&mut``` methods that are not thread safe, but no ```&```-methods that are not thread safe. An example would be a version of ```Rc``` with a ```clone_mut()``` method that took ```&mut self``` and no other ```clone()``` method. It could be thread-safely shared provided that a ```&mut``` reference was not sent to another thread, since then it could only be cloned in its original thread and could not be dropped while shared (hence, it is ```Sync```), but a mutable reference could not, nor could it be moved into another thread (hence, it is not ```Send```). However, because ```&T``` is Send if ```T``` is Sync (per the new definition), adding a ```Send``` bound will guarantee that only shared pointers of this type are moved between threads. | ||
|
||
Thirdly, we'd add an ```Own``` trait as specified above. This would be used mostly as a convenience in user code for the current cases where ```Send``` is being used as a proxy for ```Send + 'static```. We would probably still want to use ```Send + 'static``` as an explicit bound in most cases, just in case a user implemented ```Own``` but not ```Send```. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Send + 'static
is used as the explicit bound in most cases, where would Own
be used? (I.e. is it useful?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Own
wasn't part of the original proposal. I added it after Niko suggested it might be useful for, e.g., where Box<Send>
is used today, but as I commented earlier today I'm not sure how useful this really is. I don't really see any harm in its existence either way.
Metapoint: you can use a single backtick for inline code |
I just played around with rustc a bit and you actually can get static FOO: [uint, .. 2] = [0, 0];
static BAR: &'static mut [uint, .. 2] = & FOO;
static BAZ: &'static mut [uint, .. 2] = & FOO;
println!("{} == {}", BAR as *mut _, BAZ as *mut _); I think this is actually a bug, since we have aliased |
I filed rust-lang/rust#18939 about part of that bug. |
guaranteed to be threadsafe by Rust's type system. | ||
|
||
Another important note is that with this definition, `Send` will fulfill the proposed role of `Sync` in a fork-join concurrency library. At present, to use `Sync` in a fork-join library one must make the implicit assumption that if `T` is `Sync`, `T` is `Send`. One might be tempted to codify this by making `Sync` a subtype of `Send`. Unfortunately, this is not always the case, though it should be most of the time. A type can be created with `&mut` methods that are not thread safe, but no `&`-methods that are not thread safe. An example would be a version of `Rc` with a `clone_mut()` method that took `&mut self` and no other `clone()` method. It could be thread-safely shared provided that a `&mut` reference was not sent to another thread, since then it could only be cloned in its original thread and could not be dropped while shared (hence, it is `Sync`), but a mutable reference could not, nor could it be moved into another thread (hence, it is not `Send`). However, because `&T` is Send if `T` is Sync (per the new definition), adding a `Send` bound will guarantee that only shared pointers of this type are moved between threads. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've now managed to parse this correctly, but perhaps it could be reworded for greater clarity. In particular I was having trouble with "but a mutable reference could not" - could not what? (Be thread-safely shared.)
And "It could be thread-safely shared provided that a &mut
reference was not sent to another thread" - but what if one was? (The answer is apparently that it couldn't have been, because &mut T
is not Send
. Again, this was stated, but it took me a while to understand.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify what's being asked here--do you want clarification on what this means from me, or just a change of wording in the RFC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my understanding (in parentheses) is correct, then just a change of wording.
This seems like a worthwhile generalization as far as I understand it, but as noted above I don't understand it all the way to the finer points. Could you try to give the one-sentence descriptions of what |
|
||
Another important note is that with this definition, `Send` will fulfill the proposed role of `Sync` in a fork-join concurrency library. At present, to use `Sync` in a fork-join library one must make the implicit assumption that if `T` is `Sync`, `T` is `Send`. One might be tempted to codify this by making `Sync` a subtype of `Send`. Unfortunately, this is not always the case, though it should be most of the time. A type can be created with `&mut` methods that are not thread safe, but no `&`-methods that are not thread safe. An example would be a version of `Rc` with a `clone_mut()` method that took `&mut self` and no other `clone()` method. It could be thread-safely shared provided that a `&mut` reference was not sent to another thread, since then it could only be cloned in its original thread and could not be dropped while shared (hence, it is `Sync`), but a mutable reference could not, nor could it be moved into another thread (hence, it is not `Send`). However, because `&T` is Send if `T` is Sync (per the new definition), adding a `Send` bound will guarantee that only shared pointers of this type are moved between threads. | ||
|
||
Thirdly, we'd add an `Own` trait as specified above. This would be used mostly as a convenience in user code for the current cases where `Send` is being used as a proxy for `Send + 'static`. We would probably still want to use `Send + 'static` as an explicit bound in most cases, just in case a user implemented `Own` but not `Send`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is that possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not actually sure whether it's possible or not--I might have meant to switch this (that is, it is possible that a user could have Send
implemented, but not Own
, not the other way around, in which case we'd still want to allow it to be sent across threads).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Own
is just defined literally as a synonym for Send + 'static
, i.e.
trait Own: Send + 'static { }
impl<T: Send + 'static> Own for T { }
then I don't think either issue arises. (This is basically the definition from the RFC, but I'm not sure why we'd need unsafe
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right and we don't need unsafe. That definition went through several iterations. I'll adjust the RFC. However, I still don't think we want to use Own
as an explicit bound, because it is still possible that someone could deliberately write a negative impl for Own
(I'm not sure why you would though, so maybe that's not really a case worth considering).
On second thoughts, we actually aren't using a default trait here so we can't write a negative impl. So I think you're right and this is just a nonissue. Maybe then Own
would have some convenience in library types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's what I would hope.
The differences are precisely stated in the introductory section, but essentially the meanings would be identical to what they are now-- I was trying to look for a simple way to define |
I think I get it now, thanks. |
fn xyz_play(s: *mut Xyz); | ||
} | ||
|
||
pub struct Xyz(marker::NoCopy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this type, as written, is actually unsafe, as mem::swap
can be applied to two instances of it. However, if there were some sort of marker::NoSized
, this example should be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just went through the original logs and this is still safe in this particular case since all the markers are non sized (I think).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct Xyz
is zero-sized. You can swap around instances of it as much as you want – it won't do anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*
can still be used to get a Xyz
rvalue here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some sense, but in safe code it can't be copied, created (except via xyz_create()), moved out of, or dropped, so you can't actually do anything with it.
The proposed |
Hmm, I wonder if (I'm sure other libraries like |
However, |
Oh, right. Is there anything in safe Rust that's not reentrant, then? |
You could create a non-reentrant function by using a static |
```rust | ||
impl<'a, T> !Send for &'a T {} | ||
|
||
unsafe impl<'a, T> Send for &'a T where T: Sync + 'a {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this change also add corresponding impls for &mut T
? (Seeing how you can't send non-'static
&mut T
today either)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way default trait implementations work, Send
will automatically be implemented for &mut T
unless it explicitly opts out, so I did not include that in the pseudocode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so &mut T
would just always implement Send
, as opposed to today?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, subject to the rules outlined in the RFC (the default implementation only exists if T
is already Send
, since that's how default traits are proposed to work).
So I gave some thought to this RFC. As you know I am in agreement that I have some comments. I'll try to separate them. This comment is pub struct RcRef<'x,T> {
r: &'x Rc<T> // private
}
// RcRef is safe to send/share and so forth
// so long as T is synchronous. This is because
// its API does not permit any action that would
// affect the ref-count of the inner `Rc`.
impl<'x,T:Sync> Send for RcRef<'x,T> { }
impl<'x,T:Sync> Sync for RcRef<'x,T> { }
// We can even copy/clone the `RcRef`,
// which still will not touch the reference count.
impl<'x,T> Copy for RcRef<'x,T> { }
impl<'x,T> Clone for RcRef<'x,T> {
fn clone(&self) -> RcRef<'x,T> { *self }
}
impl Deref<T> for RcRef<'x,T> {
fn deref(&self) -> &T {
&*self.r
}
} So basically I think that while the In talking things over with @aturon and @alexcrichton, we came to the
Basically, you can implement
In all cases these are interior properties. So for example a Note that different bits of reachable memory can be justified under It's nice that when you define a type you can decide whether it is These definitions seem like things that data structure authors will be Anyway, what do you think of that? |
I realize now that my definitions were slightly off in that they fail to account for the API. That is, memory which is reachable but not accessible through the API doesn't count. |
@arielb1 yes. Also, I was thinking more and I also realized that while what I wrote is true regarding the ability to express a pattern like |
(That said, I think that "values reachable from" is still the right basis for a definition, but as I wrote above it really has to take into account the effects of operations that can be performed on those values.) |
But really what I'm most concerned with is whether Sync => Send is a good idea or not. I imagine we can have several definitions for Send/Sync ranging from less to more precise. |
There was one other thing that @aturon and @alexcrichton and I were talking about. We were thinking that in the standard library, at least, the However one place where Anyway, the question then is, beyond object types, what other places are there that might want |
As you said, As far as |
Also, as a side note: it seems to me that the "immutable" in "immutable OR synchronized" is really redundant, because if something is immutable clearly all mutation of it satisfies any property you might choose. But I may be missing something there. Edit: Oh right, "immutable" in this context doesn't precisely mean "immutable", but "lacks interior mutability." So never mind :) |
I mentioned Futures earlier in this thread. Upon further reflection I think that just about all forking, spawning, and data parallelism can be done in terms of them. impl<'a, A, F> Future<'a, A> where F: FnOnce(): Send + 'a -> A {
/// Dropping the future thus constitutes a join
fn spawn(blk: F) -> Future<'a, A>;
...
}
impl Future<'static, ()> {
/// Drop the future without blocking on the child thread's termination
fn forget(self);
}
|
fn parallel_inplace_map<T: ?, Closure: Fn(&mut T) + Sync>(closure: &Closure, &mut[T]);
Edit: Sorry for my confusion. |
We are talking about Sync → Send, not Send → Sync (which is clearly false, e.g. because of |
@pythonesque Btw, I wanted to let you know that I'll be helping "shepherd" this RFC. The core team is extremely overloaded right now trying to get together the alpha (in two weeks), but I'm hoping we can make progress on the backwards-incompatible changes before then. I will be in touch again soon. |
_Edit:_ Don't mind me, pretty sure everything I said in this comment was the product of a brain fart :) |
@pythonesque BTW at this stage it's looking unlikely that we'll be ready to make a change before alpha (Jan 9), but I will avoid stabilizing affected APIs. We'll need to come to a decision soon after alpha. |
@pythonesque can you adjust the RFC to remove mention of the |
I'm happy to report that this RFC has been merged a long last! The response has long been positive, and many have been looking forward to this change. Landing the RFC was previously blocked on default lifetime bounds, but the relevant RFC has been accepted and the change should be merged soon. Implementing this change should be relatively straightforward, but the audit will take some time. |
Previously Send was defined as `trait Send: 'static {}`. As detailed in rust-lang/rfcs#458, the `'static` bound is not actually necessary for safety, we can use lifetimes to enforce that more flexibly. `unsafe` code that was previously relying on `Send` to insert a `'static` bound now may allow incorrect patterns, and so should be audited (a quick way to ensure safety immediately and postpone the audit is to add an explicit `'static` bound to any uses of the `Send` type). cc rust-lang#22251.
Rendered view.
This RFC proposes extending the
Send
trait in some relatively small but backwards incompatible ways, to allow more safe behaviors to be exposed in Rust's type system. In particular, this involves removing the'static
bound from Send in a way that preserves thread safety.