EDIT: This proposal is currently withdrawn, and I think a variation of RFC PR 248 is a better solution for the problems at hand. But ~
as a semi-explicit coercion operator can still be added later for user definable coercion rules, if they are implemented in Rust in the future.
Rust prefers to be explicit, but there are times when being too explicit can be ergonomic problems. Particularly, recently we are talking about introducing more coercions into the language:
RFC PR 225: Add safe integral auto-coercions. RFC PR 226: Allow implicit coercions for cross-borrows. RFC PR 241: Deref coercions.
Also discussions about introducing a Coerce
trait.
The problems of implicit coercions is they may be too implicit. When we see a function call foo(bar, baz);
, we will not be sure if the compiler is moving bar
and baz
, or taking their references, or launching nuclear missiles even before the body of foo
gets executed.
This goes against Rust’s philosophy.
So, being too explicit and too implicit are both undesirable sometimes, then what to do?
@nrc’s idea about repurposing &
to be a borrow operator got me thinking, that all we needed might be a sigil providing a middle ground.
Say we have a function fn foo(bar: &T)
, and a value fancy: Very<Deep<Chain<Of<Wrappers<T>>>>>
, and we want to apply foo
to the “meaningful” content of fancy
.
We can do it explicitly, foo(&*****fancy)
, or implicitly with proposed implicit coercions: foo(fancy)
.
Both have their own problems. But usually the programmer doesn’t really care about exactly how many *
's he/she has to write, all he/she wants to know is that “some deferencing/coercions are happening and I may have to be cautious”.
So I propose we introduce a sigil for saying “some coercion magic happens here”, just like how !
in macros means “some code transformation magic happens here”.
And that sigil would be ~
. The rule is:
- the language can define arbitrary coercion rules, even make them user-definable with a trait, but the only coercion rule that the language implicitly applies is auto dereferencing self in method calls;
- when a value is used in a position where possible coercions can be applied, the programmer must opt-in with
~
, a prefix unary operator; - it is a compile error to use successive
~
s or use it when no coercion rules can apply.
So we can say foo(~fancy)
then.
Why ~
? It is unused and lightweight, and implies “slight transformations” in my eyes.
Why not ~
? Because some keyboard layouts do not have a ~
key.
Personally I believe this is a good compromise between “no coercions” and “implicit coercions”.
Also, I think ~
can help with a problem introduced by auto dereferencing self
in method calls:
If we have a bar: Rc<Foo>
, when we call bar.clone()
, which clone are we calling?
I propose that we use ~
as a “reverse coercion” operator in the suffix position here. The rules are:
-
foo.bar()
always calls thebar
method on the “meaningful content”; - if we want to call a
bar
method on some wrapper along the auto-deref chain, we use the syntax:foo~.bar()
; - if there are multiple
bar
s defined in the wrappers along the chain,foo~.bar()
causes a compile error. - Further disambiguation should be done with features like UFCS.
Also a good compromise in my eyes.
So, do you think these are good ideas or not?