What is turbofish syntax

Turbofish syntax is the ::<> notation in Rust used to explicitly define generic type parameters when the compiler cannot infer them.

When the compiler needs a hint

You read a line of text from a file. It contains "42". You want to turn that string into an integer so you can do math. You write "42".parse(). The compiler rejects you immediately. It doesn't know what type you want. An integer? A float? A date? A custom struct that implements parsing?

You try parse(i32). Syntax error. You look at the documentation and see parse::<i32>(). That fish-shaped symbol ::<i32> is the turbofish. It tells the compiler exactly which generic type to use when inference fails.

What turbofish actually does

Rust functions can be generic. A generic function is a template that works with any type, as long as the type meets certain requirements. The compiler fills in the template with concrete types when you call the function. Most of the time, Rust infers the types automatically by looking at how you use the result.

Inference flows backward from usage. If you write let x: i32 = parse(), the compiler sees the i32 on the left and fills the generic hole with i32. If you write let x = parse(), there is no type information. The compiler has no constraints. It cannot generate code because it doesn't know the size of the value, how to drop it, or which methods are available.

Turbofish syntax ::<Type> provides the type parameter explicitly at the call site. It attaches to the function or method name, not the value. The name comes from the visual shape: :: is the path separator, and <> are the generic brackets. Together, ::< looks like a fish swimming to the left.

Minimal example

fn main() {
    // Inference works here. The binding type constrains the generic parameter.
    let v: Vec<i32> = Vec::new();

    // Inference fails here. There is no binding type and no usage to constrain T.
    // let v = Vec::new(); // E0282: cannot infer type

    // Turbofish provides the type directly to the compiler.
    let v = Vec::<i32>::new();

    // Parsing a string requires the target type.
    // The turbofish specifies that we want an i32.
    let num: i32 = "42".parse::<i32>().unwrap();
}

How inference and turbofish interact

The compiler treats generic arguments as variables in a constraint system. When you call a generic function, the compiler creates a placeholder for each type parameter. It then scans the surrounding code for constraints. A constraint comes from a type annotation, a trait bound, or a usage pattern like passing the value to another function.

If the constraint system can solve for all placeholders, the code compiles. If a placeholder remains free, the compiler emits E0282 (type annotations needed). Turbofish adds a direct constraint to the placeholder. It says "this parameter is exactly this type."

Turbofish is not limited to a single type. Functions with multiple generic parameters can specify all of them, or use wildcards to let inference handle the rest.

fn pair<T, U>(t: T, u: U) -> (T, U) {
    // Returns a tuple containing both values.
    (t, u)
}

fn main() {
    // Both types are inferred from the arguments.
    let p = pair(1, 2.0);

    // Explicitly specify both types using turbofish.
    let p = pair::<i32, f64>(1, 2.0);

    // Specify only T. U is inferred from the second argument.
    // The underscore tells the compiler to infer this parameter.
    let p = pair::<i32, _>(1, 2.0);
}

Realistic usage patterns

Turbofish appears most often in three scenarios: parsing, collection initialization, and disambiguating Option or Result constructors.

Parsing is the classic case. The parse method on &str is generic over the target type. You almost always need turbofish here unless the binding annotation provides the type.

fn read_config(key: &str) -> Result<i32, std::num::ParseIntError> {
    // The function return type constrains the parse call.
    // No turbofish needed because the compiler knows the result must be i32.
    let value = std::env::var(key).unwrap_or_default();
    value.parse()
}

fn log_value(key: &str) {
    // The result is discarded. No binding, no return type constraint.
    // Inference fails. Turbofish is required.
    let value = std::env::var(key).unwrap_or_default();
    let _ = value.parse::<i32>();
}

Collection initialization often uses Vec::new(). If you push elements later, the type is inferred from the first push. If you never push, or you need the type for a method call before pushing, turbofish resolves the ambiguity.

fn main() {
    // vec![] macro infers type from elements.
    let v = vec![1, 2, 3];

    // Vec::new() has no elements to infer from.
    // Turbofish specifies the element type.
    let v = Vec::<i32>::new();

    // Alternatively, use a binding annotation.
    let v: Vec<i32> = Vec::new();
}

Option::Some and Result::Ok can be ambiguous when the inner value has multiple possible types. Integer literals are particularly tricky because 1 could be i32, u64, isize, or any integer type.

fn main() {
    // 1 is ambiguous. The compiler doesn't know which integer type to use.
    // let x = Some(1); // E0282: cannot infer type

    // Turbofish forces the inner type to i32.
    let x = Some::<i32>(1);

    // Binding annotation also works and is often preferred.
    let x: Option<i32> = Some(1);
}

Pitfalls and compiler errors

The most common error is E0282. This happens when you forget the turbofish or the binding annotation. The compiler message points to the generic function call and says it cannot infer the type. The fix is to provide the type via turbofish or annotation.

Placement matters. Turbofish attaches to the type name, before the method name. Writing Vec::new::<i32>() is a syntax error. The correct form is Vec::<i32>::new(). The fish goes on the generic type, not the method.

fn main() {
    // Correct: turbofish on the type Vec.
    let v = Vec::<i32>::new();

    // Incorrect: turbofish after the method name.
    // let v = Vec::new::<i32>(); // Syntax error
}

Redundancy is a style issue. Writing let v: Vec<i32> = Vec::<i32>::new(); compiles but repeats the type twice. The community convention is to use the binding annotation when available. It reads more naturally and keeps the call site clean. Turbofish is reserved for places where binding annotations are impossible, such as method chains or function arguments.

fn main() {
    // Preferred: binding annotation is clear and concise.
    let v: Vec<i32> = Vec::new();

    // Acceptable: turbofish used when no binding exists.
    let v = Vec::<i32>::new();

    // Redundant: avoid repeating the type.
    // let v: Vec<i32> = Vec::<i32>::new();
}

Another pitfall is assuming turbofish changes the value. Turbofish only specifies the generic parameter. It does not cast or convert the value. If you need to change types, use as casts or trait methods like From or TryInto.

fn main() {
    // Turbofish specifies the type of the Option, not a conversion.
    let x = Some::<i32>(42);

    // To convert a value, use explicit conversion.
    let y: f64 = 42.into();
}

When to use turbofish

Use type annotations on the variable binding when you are assigning the result to a variable. let x: Type = func(); is cleaner than let x = func::<Type>(); and reads like natural language.

Use turbofish syntax when the generic type cannot be inferred from usage and no binding annotation is available. This includes method chains, function arguments, macro invocations, and standalone expressions where the type is not constrained by context.

Use turbofish for Option::Some and Result::Ok when the inner value is ambiguous and the surrounding context does not constrain the type. Some::<i32>(42) resolves ambiguity where Some(42) fails with E0282.

Use partial turbofish with wildcards when a function has multiple generic parameters and you only need to specify some of them. func::<i32, _>() specifies the first parameter and lets the compiler infer the rest.

Reach for explicit type parameters in function signatures when writing generic code. The turbofish is for call sites. Definitions use <T> in the signature, not turbofish.

Where to go next