pcwalton

Occasional notes on Rust, Firefox, etc.

The Two Meanings of "Impl"

| Comments

impl declarations in Rust have two forms. The subtle distinction between the two can be confusing at first, so I’ll briefly explain the difference here.

The first form of impl is a type implementation. (Earlier I was calling this an “anonymous trait”, but I think that this terminology is probably more confusing than it’s worth.) This form allows you to define new functions associated with a type. For example:

struct Dog {
    name: ~str
}

impl Dog {
    static fn new(name: ~str) -> Dog {
        return Dog { name: name };
    }

    fn speak(&self) {
        io::println("woof");
    }
}

This example defines new functions new and speak under the Dog namespace. Here’s an example of their use:

let dog = Dog::new("Snoopy");
Dog::speak(&dog); // note: doesn't work today, see note below

(The explicit call of the form Dog::speak(&dog) doesn’t work today, but I wrote it out to emphasize the fact that speak lives in the Dog namespace. It’s likely to work in the future, though. Today, you need to write dog.speak().)

The second form of impl, on the other hand, is a trait implementation. It’s distinguished from the first form by the presence of a : followed by the name of a trait. This form allows you to provide an implementation for one or more existing functions belonging to a trait. It doesn’t define any new functions. For instance, suppose I defined this trait:

trait Animal {
    static fn species(&self) -> ~str;
}

Then I can supply an implementation of species() for my Dog structure like this:

impl Dog : Animal {
    static fn species(&self) -> ~str {
        return ~"Canis lupus familiaris";
    }
}

The key point to notice here is that this form doesn’t define any new names. This code won’t compile:

let dog = Dog::new("Fido");
io::println(Dog::species(&dog)); // unresolved name: `species`

But this code will:

let dog = Dog::new("Spot");
io::println(Animal::species(&dog));

The reason is that a trait implementation only provides the implementation of one or more existing functions rather than defining new functions. The function species is part of the Animal trait; it’s not part of Dog.

(You might reasonably ask: Why not duplicate the name species into Dog, for convenience? The reason is because of name collisions: it should be possible to implement Animal and later implement another trait with a different function called species without breaking existing code.)

So the upshot of this is that there are two forms of implementations in Rust: the type implementation, which defines new functions, and the trait implementation, which attaches functionality to existing functions. Both use the impl keyword, but they’re different forms with different meanings.

Comments