What is self in a trait with generics?

94 Views Asked by At

The goal: implement a default implementation of a trait for any type which has a to_string().

So my approach might actually be completely wrong. But I am asking the question this way first because it's an interesting one nonetheless.

use core::fmt::Display

trait MyStyle<T: Display> {
  fn pretty(&self) -> String {
     let buf = self.to_string();
     // manipulate buf
     buf
  }
}

In the above code, what exactly is self? My env (through rust-analyzer) just tells me self: &Self // size = 8, align = 0x8. But what type is it?

Obivously this code doesn't work, or I wouldn't be here, because I get

the method to_string exists for reference &Self, but its trait bounds are not satisfied.

If it says to_string exists, that suggests that self could be some reference to Display, but I don't understand the rest, and why it does not compile.

3

There are 3 best solutions below

2
kmdreko On BEST ANSWER

Self in a trait refers to the type implementing that trait. Thus, in the trait definition it represents any type that implements that trait. You could think of it as a generic S where S: MyTrait<T>. It does not refer to one singular type.

The goal: implement a default implementation of a trait for any type which has a to_string().

This is pretty simple and you may have over complicated things by making your trait generic. This is likely what you want:

use std::fmt::Display;

trait MyStyle: Display {
    fn pretty(&self) -> String {
        let buf = self.to_string();
        // manipulate buf
        buf
    }
}

Likely with an additional impl<T> MyStyle for T where T: Display {} to actually create a blanket implementation for anything that implements Display.

2
vallentin On

the method to_string exists for reference &Self, but its trait bounds were not satisfied the following trait bounds were not satisfied: Self: std::fmt::Display

The wording is just a bit confusing. The compiler is not saying that the method to_string() exists for &Self. It's saying that the method to_string() exists, period. The name to_string() refers to an actual method.

For instance if you instead called self.foo_bar_baz() (which doesn't exist).

let buf = self.foo_bar_baz();

Then the compiler would say:

no method named foo_bar_baz found for reference &Self in the current scope method not found in &Self

Because a method named foo_bar_baz doesn't exist. So it's only saying that to_string() exists. Not that it specifically exists for &Self.


Separately, the compiler is saying to_string() exists. However, the trait bounds Self: std::fmt::Display are not satisfied. So it is not possible to call .to_string() for &Self.

1
alter_igel On

In a trait impl, &self is basically syntactic sugar for self: &Self, and Self is a placeholder type for the actual type that implements that trait. This is especially apparent in traits that define constructor-like associated methods, like the Default trait, which is essentially:

trait Default {
    fn default() -> Self;
}

struct A {};
impl Default for A {
    fn default() -> A { // NOTE: Self == A here
        A {}
    }
}

Beware that adding trait bounds on a generic trait is quite restrictive. What's more idiomatic is to make the trait completely generic, and then define a blanket impl. For example

trait MyStyle {
     fn pretty(&self) -> String;
}

impl<T> MyStyle for T where T: Display {
    fn pretty(&self) -> String {
        let buf = format!("-~<[{}]>~-", self);
        buf
    }
}


fn main() {
    println!("{}", 123.pretty());
}

Prints:

-~<[123]>~-

Live Demo