Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: Improve the Send trait. #458
Conversation
This comment has been minimized.
This comment has been minimized.
|
Hmm, this sounds very nice! What does your |
This comment has been minimized.
This comment has been minimized.
|
@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). |
This comment has been minimized.
This comment has been minimized.
|
Cool, I did not know about that library. So under your proposal, |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
Gotcha. Sounds good either way. |
This comment has been minimized.
This comment has been minimized.
|
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 |
pnkfelix
assigned
nikomatsakis
Nov 13, 2014
huonw
reviewed
Nov 13, 2014
| 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```. |
This comment has been minimized.
This comment has been minimized.
huonw
Nov 13, 2014
Member
If Send + 'static is used as the explicit bound in most cases, where would Own be used? (I.e. is it useful?)
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 13, 2014
Author
Contributor
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.
This comment has been minimized.
This comment has been minimized.
|
Metapoint: you can use a single backtick for inline code |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
I filed rust-lang/rust#18939 about part of that bug. |
glaebhoerl
reviewed
Nov 14, 2014
| 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. | ||
|
|
This comment has been minimized.
This comment has been minimized.
glaebhoerl
Nov 14, 2014
Contributor
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.)
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 14, 2014
Author
Contributor
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?
This comment has been minimized.
This comment has been minimized.
glaebhoerl
Nov 14, 2014
Contributor
If my understanding (in parentheses) is correct, then just a change of wording.
glaebhoerl
reviewed
Nov 14, 2014
| preserving current behavior. I don't think this would be too difficult, but it may be that there | ||
| are some edge cases here where it's tricky to determine what the right solution is. | ||
|
|
||
| ## More unusual types |
This comment has been minimized.
This comment has been minimized.
glaebhoerl
Nov 14, 2014
Contributor
I'm having a hard time understanding this whole section, so that I can't even point to particular parts which are difficult. Perhaps more exposition and more about the motivations might help.
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 14, 2014
Author
Contributor
This is more or less an information dump of an IRC session between @nikomatsakis , arielb1, and myself--I added it to the RFC because Niko said he wanted to make sure he remembered what we were talking about when scanning it. I can clarify further if you want, but essentially there are various safe types that make it hard to make a more intuitive version of Send and Sync than what we currently have.
glaebhoerl
reviewed
Nov 14, 2014
| pub struct Xyz(marker::NoCopy); | ||
| impl Xyz { | ||
| pub fn new() -> &'static mut Xyz { |
This comment has been minimized.
This comment has been minimized.
glaebhoerl
Nov 14, 2014
Contributor
Wait, what - weren't we saying that &'static mut is unsafe by definition?
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 14, 2014
Author
Contributor
I said it couldn't be constructed in safe code, which is true (and we don't have any standard library types that work like this). But it might still be possible to write safe types that use &'static mut. This type is thanks to arielb1, so please ask him.
This comment has been minimized.
This comment has been minimized.
|
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 |
glaebhoerl
reviewed
Nov 14, 2014
|
|
||
| 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`. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 14, 2014
Author
Contributor
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).
This comment has been minimized.
This comment has been minimized.
glaebhoerl
Nov 14, 2014
Contributor
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?)
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 14, 2014
Author
Contributor
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?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
|
I think I get it now, thanks. |
gereeter
reviewed
Nov 15, 2014
| fn xyz_play(s: *mut Xyz); | ||
| } | ||
| pub struct Xyz(marker::NoCopy); |
This comment has been minimized.
This comment has been minimized.
gereeter
Nov 15, 2014
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.
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 16, 2014
Author
Contributor
Just went through the original logs and this is still safe in this particular case since all the markers are non sized (I think).
This comment has been minimized.
This comment has been minimized.
arielb1
Nov 19, 2014
Contributor
struct Xyz is zero-sized. You can swap around instances of it as much as you want – it won't do anything.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pythonesque
Nov 30, 2014
Author
Contributor
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.
This comment has been minimized.
This comment has been minimized.
|
The proposed |
This comment has been minimized.
This comment has been minimized.
|
Hmm, I wonder if (I'm sure other libraries like |
This comment has been minimized.
This comment has been minimized.
|
However, |
This comment has been minimized.
This comment has been minimized.
|
Oh, right. Is there anything in safe Rust that's not reentrant, then? |
This comment has been minimized.
This comment has been minimized.
|
You could create a non-reentrant function by using a static |
Kimundi
reviewed
Dec 11, 2014
| ```rust | ||
| impl<'a, T> !Send for &'a T {} | ||
| unsafe impl<'a, T> Send for &'a T where T: Sync + 'a {} |
This comment has been minimized.
This comment has been minimized.
Kimundi
Dec 11, 2014
Member
Would this change also add corresponding impls for &mut T ? (Seeing how you can't send non-'static &mut T today either)
This comment has been minimized.
This comment has been minimized.
pythonesque
Dec 11, 2014
Author
Contributor
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pythonesque
Dec 11, 2014
Author
Contributor
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).
This comment has been minimized.
This comment has been minimized.
|
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? |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
(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.) |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
As you said, As far as |
This comment has been minimized.
This comment has been minimized.
|
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 :) |
This comment has been minimized.
This comment has been minimized.
|
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);
}
|
This comment has been minimized.
This comment has been minimized.
|
fn parallel_inplace_map<T: ?, Closure: Fn(&mut T) + Sync>(closure: &Closure, &mut[T]);
Edit: Sorry for my confusion. |
This comment has been minimized.
This comment has been minimized.
|
We are talking about Sync → Send, not Send → Sync (which is clearly false, e.g. because of |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
_Edit:_ Don't mind me, pretty sure everything I said in this comment was the product of a brain fart :) |
This comment has been minimized.
This comment has been minimized.
|
@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. |
raindev
referenced this pull request
Jan 13, 2015
Closed
Fix intro examples compilation warnings #21121
huonw
referenced this pull request
Jan 25, 2015
Closed
Unboxed closure: infer `move` when passing an unboxed closure to Fn* + Send #18799
This comment has been minimized.
This comment has been minimized.
|
@pythonesque can you adjust the RFC to remove mention of the |
This was referenced Feb 12, 2015
aturon
merged commit 410f741
into
rust-lang:master
Feb 13, 2015
This comment has been minimized.
This comment has been minimized.
|
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. |
pythonesque commentedNov 10, 2014
Rendered view.
This RFC proposes extending the
Sendtrait 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'staticbound from Send in a way that preserves thread safety.