Opaque type

From Rust Community Wiki
Jump to navigation Jump to search

Opaque types are types that implement certain traits, but their concrete, underlying type is not exposed to the programmer. In contrast to trait objects, opaque types are zero-cost; even though they are opaque to the programmer, the compiler knows their concrete type.

Opaque types are declared with the impl Trait syntax, which can currently be used in parameters and in return types.

Example[edit | edit source]

fn foo() -> impl ToString {
    "Hello, world!"
}

let _: &str = foo();

The above code doesn't compile. The compiler complains that the types don't match: It expected a &str, but found an opaque type impl ToString.

Trait bounds[edit | edit source]

Opaque types allow arbitrarily many trait bounds, which are separated with a +, e.g. impl ToString + Debug + IntoIterator + 'a. Opaque types can only use the functions and operations declared in their traits:

fn foo(a: impl Add<Output = i32> + Copy) {
    a + a; // OK
    a * a; // doesn't work!
}

Note that trait objects allow only one trait bound that isn't an auto trait; this is not the case with opaque types.

Use cases[edit | edit source]

There are several situations where opaque types are useful or even necessary:

Types that can't be named[edit | edit source]

Some types don't have a name, e.g. closures. When the compiler prints the type of a closure, it might looks something like [closure@src/main.rs:2:5: 2:10]. However, this syntax is not available in Rust source code. To refer to a closure, an opaque type must be used, e.g. impl Fn(i32) -> bool. The traits that are usually used are FnThis links to official Rust documentation, FnMutThis links to official Rust documentation and FnOnceThis links to official Rust documentation, because they allow calling the closure.

Generic function parameters[edit | edit source]

Generic parameters and opaque parameters are conceptually the same:

fn foo(_: impl ToString) {}
// can be written as
fn foo<T: ToString>(_: T) {}

However, the impl Trait syntax is is a bit more succinct and easier to type. You might also find it easier to read, since it involves fewer sigils.

Forward compatibility[edit | edit source]

In the return type of a function, an opaque type is often used even if the actual return type could be named. For example:

fn foo() -> impl IntoIterator<Item = i32> + Debug {
    vec![1, 2, 3]
}

The return type could just as well be written as Vec<i32>, but the opaque type guarantees that it can be replaced with a different concrete type in the future, without breaking backwards compatibility. The downside is of course that users of this function are restricted to certain operations. For example, the returned Vec can't be indexed. However, this is sometimes desirable to allow forwards compatibility.

Other uses of the term opaque[edit | edit source]

Structs that don't expose any implementation details and extern types are sometimes called opaque types (even in RFC 1861: extern types). This is discouraged, since it is ambiguous and can cause confusion[1].

References[edit | edit source]