Repository navigation
Language ergonomic/learnability improvements #17
Description
Point of contact
@nikomatsakis @aturon @withoutboats
Overview
As part of the effort to lower Rust's learning curve, we are interested in pursuing a number of language improvements that will make Rust both nicer to use and easier to learn. The goal is to enhance productivity without sacrificing Rust's other core values. See the README for more details.
A kind of manifesto for this work was written up on the main blog.
Status
There are a lot of ideas in flight! The lang team has been focused on writing RFCs for the biggest changes; there are lots of opportunities to help either there, or with more narrow RFCs. If you are interested in the ideas listed below, please contact @aturon or others on the team to hook you up with a mentor for RFC writing!
The potential improvements are organized by broad areas of Rust:
Ownership/borrowing
RFCs
- Match ownership ergonomics (@nrc, @cramertj)
- In FCP
- Non-lexical lifetimes
- Nested method calls
Sketches
- Allow owned values where references are expected (or:
AsRefcoercions) (@aturon)- Internals thread
- Good mentored RFC!
- Auto-deref
Copytypes (@aturon)- Good mentored RFC!
- Lifetime elision improvements (@nikomatsakis, @aturon, @solson)
- Internals thread
- @aturon is currently taking point on this
Traits
RFCs
- Trait aliases (@withoutboats)
- Accepted!
-
impl Traitstabilization and expansion (@aturon)- Accepted!
- More
impl Traitwork, e.g.impl Traitin traits (@aturon, @cramertj, @eddyb, @withoutboats)- @cramertj is taking point
- A dedicated repo
- Delegation of impls (@withoutboats)
- Needs significant revisions; has stalled out
- Reduce repeated constraints when writing
T: SomeTrait- Related to implied bounds
- Closed in favor of Implied bounds rfcs#2089
Sketches
- Implied bounds (@nikomatsakis)
-
dyn Traitsyntax for trait objects (@nikomatsakis, @aturon)
Module and privacy system
Landed
-
pub(restricted)is stable!
RFCs
Sketches
- Possible module system improvements (@withoutboats, @aturon)
- Follow up post and thread with further thinking
- @withoutboats and @aturon have had a bunch of new ideas here; writeup coming soon.
Error handling
RFCs
- Extending
?to work with more types (@nikomatsakis)- Accepted!
-
?inmain(@nikomatsakis)- Nearing FCP
- Coercions into
Resultwhen leaving a block (@scottmcm, @nikomatsakis, @aturon)- Good mentored RFC!
- Posted as Ok wrapping: Improved support for writing code from an error handling mindset rfcs#2107 -- controversial; heading to close
- Alternative RFC for just
Try<Ok=()>: Add the()→Result<(), _>coercion rule, for removingOk(())everywhere. rfcs#2120
Primitive types/core traits
Sketches
- Allow string literals to somehow be used as
String(@aturon)- See the internals thread
- Implicit widening on integer types (@aturon)
- Potentially deprecate
PartialEq/PartialOrd(@aturon)- See this comment for a bit more detail and rationale.
-
#[derive(Data)]- Possibly falls out of trait aliases
Type annotations/verbosity
RFCs
Sketches
- Eliding type name in
matcharms - Eliminate need for the
::infoo::<u32>(blah), in at least the single-argument case - Make coercions more applicable
- E.g., to functions
- E.g., better behavior in generic, trait-bounded contexts (this might be easy to do with Chalk)
- capture the full path in a closure, not just local variable
- @nikomatsakis would be happy to mentor this!
FFI
Landed
- Unions
- FCP period for stabilization is complete!
Other
RFCs
- Default struct field values
- Coroutines, supporting nice async/await syntax
- RFC postponed, pending some open questions; see the summary and related internals thread
- There's now a working implementation and
async/awaitnotation built on top; we are considering landing that experimentally in the compiler.
More ideas
- Improving interaction between
returnand closures - Internals thread with more ideas
- Annoyances when using Rust
Activity
aturon commentedon Feb 1, 2017
Update: added the trait aliases RFC.
aturon commentedon Feb 1, 2017
Update: added Extending
?to work with more types.aturon commentedon Feb 1, 2017
Update: added Delegation of impls
aturon commentedon Feb 1, 2017
Update: added Add
else matchIxrec commentedon Feb 7, 2017
@RUSshy Could you be more specific about what "adding class" means to you? Most languages with "classes" have dozens of distinguishable features associated with their classes, and Rust already has analogues for many of them.
bestouff commentedon Feb 7, 2017
@RUSshy why wouldn't this work:
?
(Otherwise to me, the two most useful items are the ones in "Early stage thoughts", i.e. non-lexical lifetimes and module system improvements)
Nemikolh commentedon Feb 7, 2017
@RUSshy It would give wrong impression to beginners in my opinion. One could believe that all the trait being implemented by a type would appear next to the class definition itself. But this is obviously not true (see
Sync,SendorIntofor instance).Besides how would you deal with
whereclauses? What about bounds on type that apply to only specific traitimplbut not others?Would the code really be more readable at this point?
It seems, that you would definitely end up splitting your class into multiple impls as your code evolves.
Anyway, you can actually play with this
classidea by writing a macro today. It would not look exactly as you have written it but would still save you a few braces. :)Ixrec commentedon Feb 7, 2017
I don't see any duplicate/repeated code in your example, just three impl blocks merged into the struct definition. I do see the appeal of that sugar unifying "obviously related" blocks of code, but it is just sugar, and it doesn't appear to save very much typing or confusion compared to some of the other proposed sugars such as delegation, and with that particular keyword there's definitely a risk of misleading novices. I agree with Nemikolh that experimenting with a "class macro" would make more sense for now.
Though we should probably move this discussion to https://users.rust-lang.org/ or https://internals.rust-lang.org/ since it's at most a very tiny piece of the puzzle this issue is about.
withoutboats commentedon Feb 7, 2017
My impression is that thread is for a high-level coordination of the work toward this roadmap goal. Brainstorming specific language feature ideas should be at either https://internals.rust-lang.org or an issue on the RFC repo https://github.com/rust-lang/rfcs/issues
aturon commentedon Feb 10, 2017
Update: adding reference to this thread on internals which discusses some additional ideas.
aturon commentedon Feb 10, 2017
Update: added match ownership ergonomics.
emberian commentedon Mar 3, 2017
One idea that would make working with capn proto and similar systems so so much nicer would be something like attributes as exist in C# or Python, where one can use field access syntax but underneath call functions to get/set the field. The current state of the art in Rust is explicit getters and setters, which leads to terrible ergonomics for code interacting with data at the edges of the process boundary, to the point of Java-level verbosity. In the past (pre-1.0) I know there was some discussion and rejection of this, but I'd like to bring it up again.
33 remaining items
aturon commentedon Apr 29, 2017
Added:
Resultwhen leaving a block (@nikomatsakis)shepmaster commentedon Apr 29, 2017
@aturon I've created a thread as requested. We'll see where it goes ;-)
aturon commentedon May 4, 2017
Updated to add some ideas around coercions, e.g. https://internals.rust-lang.org/t/ergonomics-initiative-discussion-allowing-owned-values-where-references-are-expected/5161/22?u=aturon
aturon commentedon Jun 7, 2017
Updated with the latest status info.
le-jzr commentedon Jun 9, 2017
Any news regarding
PartialEq/PartialOrd? I find @burdges's solution elegant and nobody suggested any problems.It would also solve the weirdness where current bound
Ord: PartialOrd<Self>means "for a type to beOrd, it must bePartialOrdfirst", whereas the true mathematical relation is thatOrdis automaticallyPartialOrd<Self>,EqandPartialEq<Self>as well, by virtue ofOrdsubsuming all those properties, so encoding the relationship in a trait bound actually makes no sense.sfackler commentedon Jun 9, 2017
@le-jzr I'd love to have automatic implementations like that - it'd also be possible to implement
Readautomatically forBufReadtypes. We may need to wait until after specialization stabilizes to be able to add them, though, since I believe you need to enable the feature to specialize an implementation.glaebhoerl commentedon Jun 12, 2017
I think this RFC ("Allow comparisons between integers of different types") is also relevant here. Much like default binding modes for
match, it takes a case where the compiler today issues errors and forces people to manually write boilerplate and makes it just do the obviously right thing instead. (Except in this case, the manual boilerplate is also highly error-prone.)suhr commentedon Jun 23, 2017
useinsidetraitdefinitions.Local
usestatements are an extremely cool rust feature. But unfortunately, there're some places where you can't use them. One of such places are trait definitions.Long story short, I want to be able to write something like this:
crumblingstatue commentedon Jun 23, 2017
@suhr: That's rust-lang/rfcs#1976, and it's about to get postponed.
burdges commentedon Jun 23, 2017
It turned out trickier than folks thought. I think you can mostly work around it using submodules judiciously.
suhr commentedon Jun 23, 2017
Well, that's unfortunate.
It's an ugly and awkward kludge that is not worth it.
dobkeratops commentedon Jul 25, 2017
I wonder if this would get a different reaction in the context of this thread.
RFC: rust-lang/rfcs#2063
link to text:
https://github.com/dobkeratops/rfcs/blob/infer-function-signatures-from-trait-declaration-into-'impl's/text/infer%20function%20signatures%20from%20trait%20declaration%20into%20impls.md
alternating between languages, I continue to find the need to micromanage traits to be irritating (i.e. the single function trait case), but strangely in haskell the typeclasses don't feel as bad. It's because the there, defining and referring to the 'type class' saves you writing details out again. Initially I found it odd, but their function signatures being split are actually clearer.
Hence this PR.
People are complaining 'you read it more than you write it' but it's the repetition that is annoying: if I'm specifying the types , I would rather it figures out the trait from them. If I've specified the trait, it makes sense to use that to infer the types. Users are going to read the trait declaration itself more surely, which has all the types, and you need to refer back to that to implement anyway, surely. Aren't you just taking up more screenspace in a useless way displaying it again. The trait defs are very easy to grep for ( i have that all working nicely in emacs), and there's rustdoc too.
Something else to mention is this mental flip of arguments in the single-function-trait case (e.g. used for overloading)..
I wish you could write the impl the other way round so that it's
impl Type:Trait<..>, so the types appear in the same order as the function definitione.g.
wouldn't that be so much bettter? the mental flip between the intent "LHS,add,RHS" and expressing the trait makes the trait so much more annoying.
Again the 'multi-parameter-typeclass' case in haskell is more pleasant, possibly because you get to choose the order, so in the case of 'a single function class' you can write the class-types in the same order as the function types. (there's extra symmetry from the fact they have no 'self', but with better ordering I don't think special-Self would be a pain)
Combined with eliding the signature types, that might be enough for me to achieve peace with this system.
burdges commentedon Jul 25, 2017
I believe mutability, references, lifetimes, etc. change that calculation somewhat from Haskell, so maybe wait until folks become more comfortable with lifetime elision for
implsrust-lang/rust#15872 ?At least one can then deduce if type elision ever confuses the reader familiar with lifetime elision. If so, type elision could be restricted to cases where lifetime elision works. And maybe it should be restricted to not doing
muts anyways.dobkeratops commentedon Jul 26, 2017
most of the time operators are just reference inputs, and the lifetime elision kicks in;
Thinking in C++, I'm used to 2 main cases which are both quite simple:
So basically the times you actually need to write lifetimes, you're doing something rarer (IMO).
Eliding information in the obvious cases will give you more 'cognitive budget' to spend on the unusual.
Also the information is still there and will surface if you hit compile.
I argue people reading code are more likely to be reading the trait, which does have the full types.
also I argue haskell's split is actually helping readability, regardless of how much type information there is
Conversely if I must write out the types then I'd prefer it to do the opposite: figure out the trait from the types.
This reminds me of years of people in C++ claiming "writing out the iterator types is good for you..", delaying the deployment of 'range based for', 'auto' ..