Frequently Requested Changes

Some ideas for language proposals come up quite often. They're attractive ideas for one reason or another, but ones that we're unlikely to add.

This page documents some of those ideas, along with the concerns that argue against them.

If something appears on this page, that doesn't mean Rust would never consider making any similar change. It does mean that any hypothetical successful proposal to do so would need to address at a minimum all of these known concerns, whether by proposing a new and previously unseen approach that avoids all the concerns, or by making an extraordinary case for why their proposal outweighs those concerns.

Hopeful proposers of any of these ideas should document their extensive research into the many past discussions on these topics and ensure they have something new to offer.

An operator for unwrap

People writing code that makes extensive use of .unwrap() often ask for a shorthand operator for it, typically something postfix involving !.

Rust already provides the ? operator for propagating errors. .unwrap() exists largely for quick-and-dirty code. We don't want to make it substantially easier than it already is to write code using .unwrap(), and we definitely don't want to add dedicated syntax for it.

An option to disable the borrow checker, or bypass it in unsafe code

People learning Rust, especially those arriving from other languages, often spend time "fighting the borrow checker". And experienced developers sometimes want to write "clever" code that the borrow checker doesn't understand.

In the course of doing so, some developers request a compiler option to "disable the borrow checker", or a way to bypass the borrow checker in unsafe code blocks.

Rust already provides a means of bypassing the borrow checker: you can write unsafe code that uses "raw pointers" (*const T or *mut T, rather than &T or &mut T). Using raw pointers, you can manipulate memory in any way you see fit, and Rust's borrow checker will do nothing to stop you; if you misuse raw pointers, you'll get crashes or incorrect behavior at runtime. Many safe Rust data structures and libraries are safe wrappers designed to encapsulate some carefully written unsafe code.

You can also defer borrow checking to runtime, with types like RefCell, Rc, and Arc. These types also allow "interior mutability": modifying a value whose type doesn't look modifiable, when the value isn't "semantically" being modified. (For instance, managing mutable internal bookkeeping for an otherwise immutable value.)

However, even in an unsafe block, Rust's normal borrowed types (&T and &mut T) still follow the same rules they do everywhere else. You can't have two mutable references to the same object at once; you can't have a mutable reference and an immutable reference at the same time; you can't use an object after giving away ownership of it. Having distinct types for safe borrows and unsafe raw pointers provides the flexibility of writing unsafe code while still getting support from the compiler in places where your code can benefit from such support.

Fundamental changes to Rust syntax

This includes proposals such as changing the generic syntax to not use </>, changing block constructs to not require braces, changing function calls to not require parentheses, and many other similar proposals. These also include proposals to add "alternative" syntaxes, in addition to those that replace the existing syntax. Many of these proposals come from people who also write other languages. Arguments range from the ergonomic ("I don't want to type this") to the aesthetic ("I don't like how this looks").

Changes that would break existing Rust code are non-starters. Even in an edition, changes this fundamental remain extremely unlikely. The established Rust community with knowledge of existing Rust syntax has a great deal of value, and to be considered, a syntax change proposal would have to be not just better, but so wildly better as to overcome the massive downside of switching.

In addition, such changes often go against one or more other aspects of Rust's design philosophy. For instance, we don't want to make changes that make code easier to write but harder to read, or changes that make code more error-prone to modify and maintain.

That said, we are open to proposals that involve new syntax, especially for new features, or to improve an existing fundamental feature. The bar for new syntax (e.g. new operators) is high, but not insurmountable. But the bar for changes to existing syntax is even higher.

Changes to avoid writing self.method() when calling a method from a method

In Rust, within a method of an object, calling another method requires writing self.othermethod(), and accessing a field requires self.field. In some other languages, such accesses can omit the equivalent of self., and just write othermethod or field, implicitly referencing the "current object". People writing code with such method calls or field accesses sometimes ask for such shorthand in Rust.

Rust prefers the explicitness of writing self. for method calls and field accesses, and in most ways treating self as a normal object of the type. This avoids unexpected calls to the wrong method, makes it easier to distinguish methods and free functions, and makes code easier to read. We don't want to provide a shorthand that makes this syntax briefer at the expense of this clarity and unambiguity.

Arbitrary custom operator syntax

Rust allows overloading existing operators; for instance, you can implement the Add trait to overload the + operator. However, Rust does not allow creating new operators.

Some would argue this can make code more readable, if you know what the operators mean. If you don't, it makes the code inscrutable.

In general, such a change would substantially raise barriers to entry for Rust developers, making Rust code less approachable and less universally understandable. We're unlikely to add support for this.

Note that Rust's existing operator overloading uses semantic trait names (impl Add), rather than symbols or names of symbols (Plus or +), which tends to encourage using overloaded operators for the same semantic purposes, rather than for building arbitrary domain-specific languages.

Rust developers seeking to build arbitrary domain-specific languages (DSLs) should consider the macro system.

Numeric overflow checking should be on by default even in release mode

Whenever possible, Rust tries to do the safe thing by default.

Numeric overflow checking (e.g. 1000u16 * 1000u16) is one case where Rust compromised on this: on many targets, numeric overflow checking has high enough overhead to hurt performance too much for a wide variety of code. As a result, Rust defaults to having overflow checking only for debug builds, while release builds have overflow checking off by default. (In release builds, numeric overflow wraps, but code cannot count on overflow checking being disabled even in release builds, as projects can turn on overflow checking in release builds. In addition, library code cannot make any assumptions about overflow checking, as the top-level compilation decides whether to enable or disable it.)

We've thought about this choice many times, and we're open to considering changes to this default based on benchmarks. If, on some Rust targets, overflow checking adds fairly little overhead on the vast majority of crates, we'd consider enabling it by default for those targets.

It would also help to have ways to detect excessive overhead caused by overflow checking (e.g. detecting numeric-heavy code) and suggesting the use of explicitly non-overflowing numeric types such as Wrapping.

Cross-function type inference

Rust's type inference generally stops at function boundaries; Rust requires specifying explicit types for function parameters, rather than allowing inference to work across functions.

This is an intentional design choice: by making functions an inference boundary, type errors become easier to debug and compartmentalize, and Rust developers can reason about code using local reasoning within a function.

Built-in / mandatory garbage collection

Adding any form of mandatory garbage collection built into the language would mandate that all targets support it, which would require some kind of "runtime". Rust gets great benefit from having no required "runtime". Rust can go anywhere, including in systems contexts where relatively few languages can.

That said, we're happy to add language features to support an optional garbage collector, where needed.

Suffix modifiers (if after return/break/continue, or after arbitrary statements)

We often get proposals for syntax like return expr if condition; or break if condition;. We don't plan to make such a change to Rust.

Such a change would prioritize concise code over readable code. We don't want people to start out thinking they're reading an unconditional return statement, and only later see the if and realize it's a conditional return.

Such a change would also have non-obvious evaluation order (evaluating the condition before the return expression).

Size != Stride

Rust assumes that the size of an object is equivalent to the stride of an object - this means that the size of [T; N] is N * std::mem::size_of::<T>. Allowing size to not equal stride may allow objects that take up less space in arrays due to the reuse of tail padding, and allow interop with other languages with this behavior.

One downside of this assumption is that types with alignment greater than their size can waste large amounts of space due to padding. An overaligned struct such as the following:

#[repr(C, align(512))] 
struct Overaligned(u8);

will store only 1 byte of data, but will have 511 bytes of tail padding for a total size of 512 bytes. This tail padding will not be reusable, and adding Overaligned as a struct field may exacerbate this waste as additional trailing padding be included after any other members.

Rust makes several guarantees that make supporting size != stride difficult in the general case. The combination of std::array::from_ref and array indexing is a stable guarantee that a pointer (or reference) to a type is convertible to a pointer to a 1-array of that type, and vice versa.

Such a change could also pose problems for existing unsafe code, which may assume that pointers can be manually offset by the size of the type to access the next array element. Unsafe code may also assume that overwriting trailing padding is allowed, which would conflict with the repurposing of such padding for data storage.

While changing the fundamental layout guarantees seems unlikely, it may be reasonable to add additional inspection APIs for code that wishes to opt into the possibility of copying smaller parts of an object -- an API to find out that copying only bytes 0..1 of Overaligned is sufficient might still be reasonable, or something size_of_val-like that could be variant-aware to say which bytes are sufficient for copying a particular instance. Similarly, move-only fields may allow users to mitigate the effects of tail or internal padding, as they can be reused due to the lack of a possible reference or pointer.

Cross-referencing to other discussions:

  • https://github.com/rust-lang/rfcs/issues/1397
  • https://github.com/rust-lang/rust/issues/17027
  • https://github.com/rust-lang/unsafe-code-guidelines/issues/176