RFC 0490: dst-syntax

lang (syntax | traits | typesystem | dst)

Summary

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.

Motivation

History of the DST syntax

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:

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.

The problem with 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.

Detailed design

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 { ... }

Drawbacks

Alternatives

Unresolved questions

None.