Continuing the discussion from Setting our vision for the 2017 cycle:
So I wrote this:
A bit on tradeoffs
I wanted to expand on those thoughts a bit here. When we think about the language, I think we have to pay attention to rounding out and smoothing the language we have; we should be very judicious about adding new features (which is not to say that I oppose new features!). In the initial roadmap post, @aturon proposed this theme for Rust in 2017:
Rust: safe, fast, productive --- pick three.
This is catchy, but it carries a really deep meaning. A lot of times when we think about "learning curve" or "new users" we tend to view those needs as being in contrast with experienced users. But I think there is a better way of looking at things. We need to work hard to find solutions that address the requirements of experienced users while keeping the learning curve in mind.
After all, we all -- experienced or n00b alike -- love using those areas of the language that work really smoothly (and there are many). As an example, I remember how the standard library felt back in the day when it was basically a dumping ground of utility functions we needed for the compiler. Then we went through the runup to 1.0, with the obsessive focus on improving ergonomics -- but not at the cost of performance or expressiveness -- and we wound up with the, frankly, awesome stdlib that we have today. This was a massive community success at overcoming this "new user vs experienced user" tradeoff.
Another example, one that I focused on in the RustConf keynote, is the closure inference in the language. It took us a while to iterate here but we have managed to have a closure system that addresses a huge diversity of needs (sync code like iterators, parallelism like rayon, async code like futures, syntax-like helpers like catch
or get_or
, etc) while still feeling very nice to use as a new user. Part of that is a lot of compiler smarts under the hood (e.g., when a variable is captured in the environment, the compiler analyzes the closure body to decide if that should be "by vaue" or not, etc).
Some things that are on my mind
What follows is a list of problems that I see as a common source of confusion for new or intermediate users. I would love to hear other candidates that aren't listed here. I'd also like to know which of these things do not belong on this list -- for example, because the concerns are too niche.
I'm adding a few notes on possible solutions but trying not to get bogged down in the details of how we would fix any particular problem. I'd like to be optimistic and just assume that with enough effort we can solve the problems to everyone's satisfaction (i.e., in a way that preserves the constraints you need for your particular problem). The biggest question to my mind is -- for which problems is it worth investing the effort to find and implement those solutions?
- The fact that string literals have type
&'static str
which cannot be readily coerced toString
- This is literally the first thing I talk about in tutorials, after "Hello, world"
- I would like a way for an
&'static str
to be coerced to aString
, ideally without allocation
ref
andref mut
on match bindings are confusing and annoying- I would be happy to never write
match *x { Some(ref y) => ... }
again but rather just writematch x { Some(y) => ... }
- would also need autoderef in some places
- @nrc and I think that a scheme like closure inference could be used here
- I would be happy to never write
- lexical lifetimes on borrows
- I think there is general agreement that something more like non-lexical lifetimes would make the borrow checker more ergonomic and nicer to use
- Fiddling around with integer sizes
- Everybody has to agree that it's annoying to deal with
usize
vsu32
and so forth - This has been discussed numerous times and there are even more complex trade-offs than usual
- But it seems like some kind of widening (perhaps with optional linting)
- Everybody has to agree that it's annoying to deal with
- References to copy types (e.g.,
&u32
), references on comparison operations- It'd be nice if we could use a
&u32
more like au32
most of the time - It sort of works:
map(|x| x + 1).filter(|&x| x > 2)
(note thatfilter
required the&
) - We may not be able to do better than progressively addressing more and more cases
- It'd be nice if we could use a
- Some kind of auto-clone
- Working with
Rc
andArc
requires a lot of manual cloning - Sometimes you want this level of control; often you don't
- The same thing will affect persistent collection libraries, which make big use of
Arc
etc - In comparison, Swift makes ref-count adjustment more automatic, and Go leverages a GC
- when targeting higher-level environments, this puts Rust at an ergonomic disadvantage
- Working with
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, etc)]
- there are a lot of fine-grained traits to derive
- easy to forget something; annoying to have to have both
PartialEq
andEq
- maybe shorthands for common combinations?
- note: custom derive may allow experimentation here
- language-level support for
AsRef
-like pattern- we often have things in the std lib where you can either give ownership or not of an argument as you choose
- example:
path.join(foo)
; herefoo
can be aPathBuf
(give ownership) or&Path
(not) or&'static str
etc - it'd be nice if we could make this pattern smoother in the language, perhaps just for some particular cases
- lifting lifetime/type parameters to modules
- often you have large blocks of code that share parameters
- I've mentioned a few times I think it would be great to float these to the module level
- should write up a more complete proposal...
- inferring
T: 'x
annotations at the type level- these are just kind of annoying and nobody has a good intution for what they mean
- I think we could infer them, just want to experiment to be sure it doesn't lead to confusing errors later on
- on the other hand, the compiler tells you what to type, and you type it, maybe not so bad?
- explicit types on statics and constants
- often we could infer these, but it'd add dependencies between items
- but then, impl trait already does
- often we could infer these, but it'd add dependencies between items
- trait aliases
- it's annoying and non-DRY to repeat a large number of constraints like
Foo+Bar+Baz
- related to inferred bounds:
- e.g., if I have
struct Foo<T: Eq>
, andfn bar<T>(x: Foo<T>)
, can the compiler just figure out thatT: Eq
must hold?- answer: yes, but we should talk about the details
- answer: yes, but we should talk about the details
- e.g., if I have
- it's annoying and non-DRY to repeat a large number of constraints like
Some new features we might consider
I think there are definitely some "new features" we should consider to make Rust more expressive. Another way to look at this is that I think sometimes the easiest way to make something more productive and ergonomic is to extend the language a bit. I talked about some of those on the main thread, but here is a list of extensions that I personally think might be important to pursue, and why:
- specialization, impl Trait
- have to finish these up
- const generics
- enables many high-performance domains
- "virtual structs" story (e.g., fields in traits, variant types)
- modeling OOP-like constructs is important for some domains and can be really challenging
- coherence limitations
- being able to implement traits for types in other crates seems like a pressing problem (e.g., @sgrif has raised this)
- negative reasoning may be part of this
- custom error messages
- when building abstractions like Diesel, Futures, and Rayon, you often wind up with bad type errors
- the problem is that the library is in a better position to give a semantic error than the compiler
- i.e., the compiler knows that
Foo: ActiveRecord
doesn't hold, but diesel might know that this means the field doesn't appear in your database schema (or whatever)
- stabilizing and pursuing placement new (
<-
) a bit more- maybe imp't for low memory domains?
- iterables and abstacting over rc vs arc etc
- not sure how to prioritize this, but RFC 1598 seems like it might be a not-that-hard approach to solving it