Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-386 · Case file with fixtures · Case 358 of 694 · Compiler evidence

A Bare Trait Path Cannot Select an Associated Function Implementation

Trait::function names a family of implementation functions but provides no Self type. Select one with <Type as Trait>::function(), or use a free function when behavior is not implementation-specific.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
A trait declaration defines a family of implementation functions, and the bare trait path does not select which implementing type owns the call.
First discriminating check
Supply the implementation with `<Type as Trait>::function()` or move genuinely universal behavior into a free function.

I defined Factory::create() without a receiver and expected to call it through the trait name. Only one implementation existed in the crate. Rust still reported E0790 because Factory::create() does not specify the corresponding implementation type.

The failing fixture keeps that “only one impl” condition intentionally. The compiler does not use it as an implicit global default.

A trait declares one function per implementation

When One implements Factory, it supplies <One as Factory>::create. Another type can supply a different function under the same trait contract. The trait item is a requirement and shared name, not one executable body selected independently of Self.

A receiver method obtains Self from the receiver expression. A receiver-free associated function has no value carrying that information. The path must supply it through a concrete type or a generic parameter.

The E0790 explanation states that a specific trait implementation is required for the call.

Current uniqueness is not a language guarantee

Counting implementations in the current crate would be unstable. A local type can receive another implementation in a different module, and public traits can be implemented for downstream local types where coherence permits.

Changing dependencies, features, or targets could also change which impls are compiled. If a bare call meant “the only one found today,” program behavior and type checking would depend on whole-build inventory in surprising ways.

Rust instead makes selection explicit and local. Adding a second implementation cannot silently redirect an existing qualified call.

Fully qualified syntax supplies Self

The repaired fixture writes:

<One as Factory>::create()

This is a qualified path naming both the concrete type and the trait implementation. If One has no conflicting inherent or trait functions, One::create() may also resolve, but the full form documents exactly which contract provides it.

In generic code I can use T::create() when T: Factory, or <T as Factory>::create() for explicitness. The type parameter is the missing selector.

Default implementations still depend on Self

A trait may provide a default body. Implementers can use or override it, and the body can refer to associated types, constants, or other functions chosen by Self. The call therefore still needs an implementation context.

If a function is truly identical and independent of every implementer, I move it to the module as a free function. This avoids creating a false type-selection question.

If it constructs Self, the concrete choice is obviously essential. A generic helper fn make<T: Factory>() can provide inference from a meaningful return or argument boundary.

This differs from an enum of registered factories

Sometimes product logic really means “use the configured factory.” A compile-time trait call does not select runtime configuration. I represent the selection with a value: an enum, a trait object with a receiver method, or a registry entry.

Converting create() to create(&self) makes dynamic selection possible because the factory value identifies the implementation. That changes architecture from type-level selection to value-level selection, which may be exactly what a plugin system requires.

I do not use global implementation counts as a hidden service locator.

Associated constants follow the same selection idea

T::LIMIT needs T for the same reason: implementations may provide different values. In a generic function, bounds can make the trait source known. Outside one, fully qualified paths remove ambiguity.

The syntax is verbose at rare ambiguous boundaries, but those boundaries are where precision pays for itself.

My first question is who chooses the implementation

If the library author chooses statically, I name a concrete type. If a generic caller chooses, I use a type parameter. If configuration chooses at runtime, I pass a value and use a receiver method. If no choice exists semantically, I use a free function.

I also avoid choosing an implementation merely to shorten the syntax. A default implementation type in application configuration should be named as that default, tested, and replaceable through the same selection mechanism as other implementations. Hiding it behind a bare trait call would make the source look universal when the behavior is actually product policy. Qualified syntax may contain a few more characters, but it gives search tools, compiler diagnostics, and future readers the exact ownership of the function.

For public examples I show the qualified form at least once. Users then learn that the function comes from a trait implementation rather than an inherent constructor. This becomes valuable when the concrete type later gains an inherent function with the same name.

The core principle is that a trait path names a contract family, not a default member. A receiver-free call contains no runtime value from which Rust can recover Self. E0790 preserves explicit implementation choice even when the current program happens to contain only one candidate.