-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Rename channel types and constructor #11765
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
Comments
I suggest:
|
I agree with with @Bascule. |
To clarify, the reason I prefer Sender and Receiver is that port/chan are words that don't carry enough meaning to disambiguate without referencing the manual. An orthogonal, but related proposal: let sender = do spawn |receiver| {
} It would presumably use a new name, not |
Huzzah for this bug! Boo for paired terminology without mnemonics. |
Copying my post on the mailing list here: The aversion to a standalone functions in this thread and in our community in general is concerning. Not everything has to be a method or associated function! There is no single thing being constructed here - both ends of the channel are being constructed at once. Whatever names we choose for the sender and receiver ends of the pipe/channel, it would be nice to have the sender first in the tuple and the receiver last. Here is an ASCII diagram to help illustrate why I think this ordering makes sense:
Not saying that The other thing we must consider is how the type names of the two ends of the pipe would be reflected in client code. let (sender, receiver) = channel();
sender.send(x);
let y = receiver.recv(); |
@bjz it's too bad there's no Call trait (yet?). let (send, receive) = channel();
send(x);
let y = receive(); |
My thoughts:
Out of these options, my vote goes to |
I agree with @tikue that |
I would also add that we should pick one name out of 'stream', 'channel', or 'pipe' for the relationship between the sender and receiver, then purge the other names from the documentation. Using multiple names can be very confusing. |
I like |
@bjz agreed. I would leave |
I think "channel" is definitely the correct term due to its heritage in CSP: |
I'm also ok with |
Yes, I would look to the terminology most in use historically for the thing that most fits with our semantics. CML, which has a messaging model extremely similar to Rust's uses the term 'channel'. http://en.wikipedia.org/wiki/Concurrent_ML let val c : string chan = channel ()
in ... end Note that CML's channel primitives are bidirectional, hence the single result returned by the |
From 3.3.1 in Concurrent Programming in ML, Reppy writes:
(* Listing 3.5: Directional channels signature *)
signature DIR_CHAN =
sig
type ’a in_chan and ’a out_chan
val channel : unit -> (’a in_chan * ’a out_chan)
val recv : ’a in_chan -> ’a
val send : (’a out_chan * ’a) -> unit
val recvEvt : ’a in_chan -> ’a event
val sendEvt : (’a out_chan * ’a) -> unit event
end; |
Is there an inherit reason why there needs to be a separation between a Sender and a Receiver (or whatever the chosen terms are)? It seems like it'd be more intuitive to have a single bidirectional channel that handles both, with appropriate methods for each action. let chan = channel(); This seems to be working great for Clojure, within the core.async library. |
@thehydroimpulse: That's what many languages do, CML, Erlang, Go, etc, but as I quoted above, even Reppy, the designer of CML says, "A safer discipline would be to distinguish between input and output accesses to a channel, so that threads could be restricted to only send or receive on a channel." It would be interesting to hear from @larsbergstrom whether he knows why bidirectional channels were chosen as CML's primitives (he has more experience with CML than I). Of course it seems to be relatively easy to build directional channels on top of bi-directional channels and vice versa. |
use std::comm::{Port, Chan};
struct BiChan<T> {
port: Port<T>,
chan: Chan<T>,
}
impl<T: Send> BiChan<T> {
fn new() -> BiChan<T> {
let (port, chan) = Chan::new();
BiChan { port: port, chan: chan }
}
fn send(&self, thing: T) {
self.chan.send(thing);
}
fn recv(&self) -> T {
self.port.recv()
}
}
fn main() {
let chan = BiChan::new();
chan.send(1);
println!("{}", chan.recv());
} Note that you can't pass these around multiple tasks because it doesn't implement |
Having a single object represent both the sending and receiving side interacts poorly with Rust's ownership rules. |
@bjz Channels are bidirectional in CML because the underlying event mechanism (which provides not only send and receive events but also acknowledgements and negative acknowledgements, is generically composable ala I agree with @wycats not only that having a single object for both ends would fail to take advantage of what Rust's type system is giving us. |
I basically agree with @bjz here.
|
Agree with @anasazi that source/sink have a weird mental effect that when you try to reason about what the words mean they kind of switch back and forth - they could each be either end. 'Sender' and 'receiver' are definitely the clearest words here but they are kind of unpalatable to me for a few reasons:
|
Also I'm kind of leaning toward |
Also, something about the nominalized (?) verbs 'sender' and 'receiver' aren't as evocative, or powerful (or something) to me as plain old nouns. Consider "I'm transmitting this message over a channel" vs. "I'm transmitting this message via a sender". This is just a vague feeling. |
I think |
This was an interesting point made about source/sink on the ml: "I am against naming it Source and Sink. It doesn't comply with the verbs we use for sending and receiving, at least for me as a non-native english speaker. While you can get/put something from/to a Source/Sink, you usually don't receive or send from a Source or Sink. The names Port and Channel are ambiguous as well, but at least you can receive (e.g. goods) from a port and send things on a channel." This is also an uncommon use of the word 'sink' (in the real world); I've occasionally encountered confusion when using it in contexts like this. |
That said, and with @glennsl's arguments, and that they prevent the stuttering problem, I'm warming up to source/sink. |
I agree with the channel() -> (Sender, Receiver) proposal. |
I think it's difficult to make any strong conclusions right now; first impressions aren't as important as long-term feelings with these things. Sure, source/sink seems a bit ambiguous at first glance, but thinking about the intent of a channel for a short period of time clears it up neatly. Similar could be said for sendr/recvr -- it seems awkward now, but perhaps people would warm up to it given time. I think that the right decision will naturally arise if we give the different proposals some time to Then again, another serious contender could still arise. Perhaps channel isn't even the most evocative metaphor that we could come up with, if that's the name of the game ( @bstrie of course has a point about bikeshedding. There are an infinite number of possibilities that we could take with this. I think, though, since |
How about let (put, take) = channel(); A downside to using verbs is how awkward it is for an object/struct. If the struct is a verb, you'd also have to have another verb for the method. |
2014年2月23日 下午3:35于 "Alex Crichton" [email protected]写道:
+1, and +1 for syncport.
|
While I agree that "port" is a nice way to describe an end of a channel, I do not think that having it in the name we provide to the user is useful; as you noticed, we'd still have to attach another word (like "Send" or "Recv") to make it meaningful. And "Recv" being shortened kinda gives it away that "Port" is useless in the name and doesn't pull its own weight but merely adds to the letters the user has to type out. "Source" and "Sink" are short and unambiguous. Conceptually, they're ports but making them "SourcePort" and "SinkPort" is just adding letters for no benefit. "Sender" and "Receiver" are also fine (though longer than "Source" and "Sink" for IMO no increase in readability); I'd strongly encourage avoiding adding more butchered shorthands like "Recv" to the standard library. Frankly, Rust already suffers for it. |
"Sink" is only unambiguous once you explain what the heck it actually means. |
Given the traditional role of "port" in Berkeley sockets, I'd consider naming the endpoints of a channel "ports" is confusing. |
On it's own, it's hard to understand what it is. But the name is not presented to the user in a vacuum. The name is presented in the context of "A Channel has a Source and a Sink endpoint." In that one sentence, it's immediately obvious to the user (now and forever) what end does what. |
Remember the POV issue. Does a Source call send or recv? It is not very clear. |
How is it an issue? The user has a Source and a Sink in their code. Per English, stuff comes out of a Source and goes into a Sink. Thus, you can get data out of the Channel from the Source and put it into the Channel through the Sink. |
Is |
+1 for source/sink (or upstream/downstream) being confusing |
The advantage of |
I disagree about the POV issue, but since I think |
I'm not super thrilled about Sender/Receiver, but I think that clarity is more important than conciceness. To reiterate @brson's comment (#11765 (comment)), this ticket needs these changes:
The only outstanding decision I can think of are what |
One concern that occurred to me tonight is that |
* Chan<T> => Sender<T> * Port<T> => Receiver<T> * Chan::new() => channel() * constructor returns (Sender, Receiver) instead of (Receiver, Sender) * local variables named `port` renamed to `rx` * local variables named `chan` renamed to `tx` Closes #11765
`needless_return_with_question_mark` ignore let-else Fixes rust-lang#11765 This PR makes `needless_return_with_question_mark` to ignore expr inside let-else. changelog: [`needless_return_with_question_mark`] ignore let-else
The current
Chan::new() -> (Port, Chan)
is confusing becausenew
returns a tuple instead of a new object, 'port' and 'chan' are not intuitive, and the name 'chan' is reused for two purposes (the sender and the total channel).Some proposed alternatives:
Chan::open() -> (Source, Sink)
Chan::new() -> (Source, Sink)
Chan::new() -> (Sender, Receiver)
Chan::new() -> (Source, Drain)
pipe() -> (Port, Chan)
Also need to consider whether sender or receiver comes first in the tuple.
Nominating.
Discussion: https://mail.mozilla.org/pipermail/rust-dev/2014-January/007928.html
The text was updated successfully, but these errors were encountered: