Skip to content

Commit

Permalink
RFC 213 is Finalize defaulted type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
aturon committed Feb 4, 2015
1 parent 39e025c commit d4e5ea9
Showing 1 changed file with 32 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
- Start Date: 2015-02-04
- RFC PR: https://github.com/rust-lang/rfcs/pull/213
- Rust Issue: https://github.com/rust-lang/rust/issues/21939

# Summary

Expand All @@ -19,7 +19,7 @@ parameters:
a suitable type variable will be used.
- Outside of a fn body, only defaulted type parameters can be
omitted, and the specified default is then used.

Points 2 and 4 extend the current behavior of type parameter defaults,
aiming to address some shortcomings of the current implementation.

Expand All @@ -34,7 +34,7 @@ Defaulted type parameters are very useful in two main scenarios:
1. Extended a type without breaking existing clients.
2. Allowing customization in ways that many or most users do not care
about.

Often, these two scenarios occur at the same time. A classic
historical example is the `HashMap` type from Rust's standard
library. This type now supports the ability to specify custom
Expand All @@ -58,7 +58,7 @@ a single `range()` function that is defined generically over all
pub fn range<A:Enumerable>(start: A, stop: A) -> Range<A> {
Range{state: start, stop: stop, one: One::one()}
}

This version is often more convenient to use, particularly in a
generic context.

Expand All @@ -80,7 +80,7 @@ default (`uint`, for example):
pub fn range<A:Enumerable=uint>(start: A, stop: A) -> Range<A> {
Range{state: start, stop: stop, one: One::one()}
}

Using this definition, a call like `range(0, 10)` is perfectly legal.
If it turns out that the type argument is not other constraint, `uint`
will be used instead.
Expand Down Expand Up @@ -135,7 +135,7 @@ type parameters declared *before* `X` in the list of type parameters:
// ERROR. Default value of `B` refers to `C`, which comes AFTER
// `B` in the list of parameters.
fn foo<A,B=C,C=uint>() { .. }

## Instantiating defaults

This section specifies how to interpret a reference to a generic
Expand All @@ -154,51 +154,51 @@ as follows:
- *Motivation:* This is consistent with Rust tradition, which
generally requires explicit types or a mechanical defaulting
process outside of `fn` bodies.

### References to generic types

We begin with examples of references to the generic type `Foo`:

struct Foo<A,B,C=DefaultHasher,D=C> { ... }

`Foo` defines four type parameters, the final two of which are
defaulted. First, let us consider what happens outside of a fn
body. It is mandatory to supply explicit values for all non-defaulted
type parameters:

// ERROR: 2 parameters required, 0 provided.
fn f(_: &Foo) { ... }

Defaulted type parameters are filled in based on the defaults given:

// Legal: Equivalent to `Foo<int,uint,DefaultHasher,DefaultHasher>`
fn f(_: &Foo<int,uint>) { ... }

Naturally it is legal to specify explicit values for the defaulted
type parameters if desired:

// Legal: Equivalent to `Foo<int,uint,uint,char,u8>`
fn f(_: &Foo<int,uint,char,u8>) { ... }

It is also legal to provide just one of the defaulted type parameters
and not the other:

// Legal: Equivalent to `Foo<int,uint,char,char>`
fn f(_: &Foo<int,uint,char>) { ... }

If the user wishes to supply the value of the type parameter `D`
explicitly, but not `C`, then `_` can be used to request the default:

// Legal: Equivalent to `Foo<int,uint,DefaultHasher,uint>`
fn f(_: &Foo<int,uint,_,uint>) { ... }

Note that, outside of a fn body, `_` can *only* be used with
defaulted type parameters:

// ERROR: outside of a fn body, `_` cannot be
// used for a non-defaulted type parameter
fn f(_: &Foo<int,_>) { ... }

Inside a fn body, the rules are much the same, except that `_` is
legal everywhere. Every reference to `_` creates a fresh type
variable `$n`. If the type parameter whose value is omitted has an
Expand All @@ -217,7 +217,7 @@ information). Here are some examples:
let x: Foo<_,_> = ...;
let x: Foo<_,_,_> = ...;
let x: Foo<_,_,_,_> = ...;

// Results in a type `Foo<int,uint,$0,char>` where `$0`
// has a fallback of `DefaultHasher`.
let x: Foo<int,uint,_,char> = ...;
Expand Down Expand Up @@ -248,7 +248,7 @@ except that it is legal to omit values for all type parameters if
desired. In that case, the behavior is the same as it would be if `_`
were used as the value for every type parameter. Note that functions
can only be referenced from within a fn body.

### References to generic impls

Users never explicitly "reference" an impl. Rather, the trait matching
Expand All @@ -275,7 +275,7 @@ fn foo<A,B=A>(a: A, b: B) { ... }
fn bar() {
// Here, the values of the type parameters are given explicitly.
let f: fn(uint, uint) = foo::<uint, uint>;
// Here the value of the first type parameter is given explicitly,
// but not the second. Because the second specifies a default, this
// is permitted. The type checker will create a fresh variable `$0`
Expand Down Expand Up @@ -359,17 +359,17 @@ do not loop over each type variable, check whether it is unbound, and
apply the default only if it is unbound. The reason for this is that
it can happen that there are contradictory defaults and we want to
ensure that this results in an error:

fn foo<F:Default=uint>() -> F { }
fn bar<B=int>(b: B) { }
fn baz() {
// Here, F is instantiated with $0=uint
let x: $0 = foo();

// Here, B is instantiated with $1=uint, and constraint $0 <: $1 is added.
bar(x);
}

In this example, two type variables are created. `$0` is the value of
`F` in the call to `foo()` and `$1` is the value of `B` in the call to
`bar()`. The fact that `x`, which has type `$0`, is passed as an
Expand All @@ -395,17 +395,17 @@ defaults, and processing defaults created trait obligations:

trait Foo { }
trait Bar { }

impl<T:Bar=uint> Foo for Vec<T> { } // Impl 1
impl Bar for uint { } // Impl 2

fn takes_foo<F:Foo>(f: F) { }

fn main() {
let x = Vec::new(); // x: Vec<$0>
takes_foo(x); // adds oblig Vec<$0> : Foo
}

When we finish type checking `main`, we are left with a variable `$0`
and a trait obligation `Vec<$0> : Foo`. Processing the trait
obligation selects the impl 1 as the way to fulfill this trait
Expand Down Expand Up @@ -438,17 +438,17 @@ The next example does 2 full iterations of the loop.
trait Foo { }
trait Bar<U> { }
trait Baz { }

impl<U,T:Bar<U>=Vec<U>> Foo for Vec<T> { } // Impl 1
impl<V=uint> Bar for Vec<V> { } // Impl 2

fn takes_foo<F:Foo>(f: F) { }

fn main() {
let x = Vec::new(); // x: Vec<$0>
takes_foo(x); // adds oblig Vec<$0> : Foo
}

Here the process is as follows:

1. Trait resolution executed to resolve `Vec<$0> : Foo`. The result is
Expand Down

0 comments on commit d4e5ea9

Please sign in to comment.