lang (syntax | traits | typesystem | dst)
Change the syntax for dynamically sized type parameters from Sized? T
to T: ?Sized
, and change the syntax for traits for dynamically sized types to trait Foo for ?Sized
. Extend this new syntax to work with where
clauses.
When dynamically sized types were first designed, and even when they were first
being implemented, the syntax for dynamically sized type parameters had not been
fully settled on. Initially, dynamically sized type parameters were denoted by a
leading unsized
keyword:
fn foo<unsized T>(x: &T) { ... }
struct Foo<unsized T> { field: T }
// etc.
This is the syntax used in Niko Matsakis’s initial design for DST. This syntax makes sense to those who are familiar with DST, but has some issues which could be perceived as problems for those learning to work with dynamically sized types:
Sized
trait, which is fundamentally
related to declaring a type as unsized (removing the default Sized
bound).Later, Felix S. Klock II came up with an alternative
syntax
using the type
keyword:
fn foo<type T>(x: &T) { ... }
struct Foo<type T> { field: T }
// etc.
The inspiration behind this is that the union of all sized types and all unsized
types is simply all types. Thus, it makes sense for the most general type
parameter to be written as type T
.
This syntax resolves the first problem listed above (i.e., it no longer implies
that the type must be unsized), but does not resolve the second. Additionally,
it is possible that some people could be confused by the use of the type
keyword, as it contains little meaning—one would assume a bare T
as a type
parameter to be a type already, so what does adding a type
keyword mean?
Perhaps because of these concerns, the syntax for dynamically sized type
parameters has since been changed one more time, this time to use the Sized
trait’s name followed by a question mark:
fn foo<Sized? T>(x: &T) { ... }
struct Foo<Sized? T> { field: T }
// etc.
This syntax simply removes the implicit Sized
bound on every type parameter
using the ?
symbol. It resolves the problem about not mentioning Sized
that
the first two syntaxes didn’t. It also hints towards being related to sizedness,
resolving the problem that plagued type
. It also successfully states that
unsizedness is only optional—that the parameter may be sized or unsized. This
syntax has stuck, and is the syntax used today. Additionally, it could
potentially be extended to other traits: for example, a new pointer type that
cannot be dropped, &uninit
, could be added, requiring that it be written to
before being dropped. However, many generic functions assume that any parameter
passed to them can be dropped. Drop
could be made a default bound to resolve
this, and Drop?
would remove this bound from a type parameter.
Sized? T
There is some inconsistency present with the Sized
syntax. After going through
multiple syntaxes for DST, all of which were keywords preceding type parameters,
the Sized?
annotation stayed before the type parameter’s name when it was
adopted as the syntax for dynamically sized type parameters. This can be
considered inconsistent in some ways—Sized?
looks like a bound, contains a
trait name like a bound does, and changes what types can unify with the type
parameter like a bound does, but does not come after the type parameter’s name
like a bound does. This also is inconsistent with Rust’s general pattern of not
using C-style variable declarations (int x
) but instead using a colon and
placing the type after the name (x: int
). (A type parameter is not strictly a
variable declaration, but is similar: it declares a new name in a scope.) These
problems together make Sized?
the only marker that comes before type parameter
or even variable names, and with the addition of negative bounds, it looks even
more inconsistent:
// Normal bound
fn foo<T: Foo>() {}
// Negative bound
fn foo<T: !Foo>() {}
// Generalising ‘anti-bound’
fn foo<Foo? T>() {}
The syntax also looks rather strange when recent features like associated types
and where
clauses are considered:
// This `where` clause syntax doesn’t work today, but perhaps should:
trait Foo<T> where Sized? T {
type Sized? Bar;
}
Furthermore, the ?
on Sized?
comes after the trait name, whereas most
unary-operator-like symbols in the Rust language come before what they are
attached to.
This RFC proposes to change the syntax for dynamically sized type parameters to
T: ?Sized
to resolve these issues.
Change the syntax for dynamically sized type parameters to T: ?Sized
:
fn foo<T: ?Sized>(x: &T) { ... }
struct Foo<T: Send + ?Sized + Sync> { field: Box<T> }
trait Bar { type Baz: ?Sized; }
// etc.
Change the syntax for traits for dynamically-sized types to have a prefix ?
instead of a postfix one:
trait Foo for ?Sized { ... }
Allow using this syntax in where
clauses:
fn foo<T>(x: &T) where T: ?Sized { ... }
The current syntax uses position to distinguish between removing and adding
bounds, while the proposed syntax only uses a symbol. Since ?Sized
is
actually an anti-bound (it removes a bound), it (in some ways) makes sense to
put it on the opposite side of a type parameter to show this.
Only a single character separates adding a Sized
bound and removing an
implicit one. This shouldn’t be a problem in general, as adding a Sized
bound to a type parameter is pointless (because it is implicitly there
already). A lint could be added to check for explicit default bounds if this
turns out to be a problem.
Choose one of the previous syntaxes or a new syntax altogether. The drawbacks of the previous syntaxes are discussed in the ‘History of the DST syntax’ section of this RFC.
Change the syntax to T: Sized?
instead. This is less consistent with things
like negative bounds (which would probably be something like T: !Foo
), and
uses a suffix operator, which is less consistent with other parts of Rust’s
syntax. It is, however, closer to the current syntax (Sized? T
), and looks
more natural because of how ?
is used in natural languages such as English.
None.