Why Colorize trait method can be called on String, while String doesn't impl Colorize

Viewed 111

From Colorize trait doc, String doesn't impl it (whereas &str impls it).

fn blue(self) -> ColoredString
where
    Self: Sized, 

But why String type can call its method?

use colored::Colorize;

fn main() {
    let blue = "blue".to_owned().blue();
    println!("{}", blue);
}


edits

  • [unresolved] How can I desugar it? (specifically, to figure out auto deref is happening)

  • What's happening under the hood?

Type coercion on method calls. String can be coerced to &str by &*String

2 Answers

The trait Colorize is implemented for &'a str, and String implements Deref<Target=str>.

Rust method call rules mean that the value might automatically be borrowed or dereferenced.

This means that for our method call on String, Rust will look for some method that has one of the following types as a receiver type

  • String
  • &String
  • &mut String
  • str (by deref)
  • &str (this one lets the method call work)
  • &mut str

Since the String is only being borrowed, you can use it after calling .blue()

use colored::Colorize;

fn main() {
    let x = "hello".to_string();
    let y = x.blue();

    println!("{}", x);
    println!("{}", y);
}

link to rust reference regarding how method calls are resolved

The reason String seems to support the traits implemented for &str is due to its implementation of Deref with the target type of str. String itself doesn't really support these traits, but the . operator acts as a sort of dereference giving access to the methods supported by &str.

Below is the minimal amount of code needed to isolate and demonstrate the same behavior.

#[derive(Debug)]
enum ColoredString { Blue }

trait Colorize {
    fn blue(self) -> ColoredString
    where
        Self: Sized,
    {
        ColoredString::Blue
    }
}

impl Colorize for &str { }  // <-- `Colorize` only impl'd for `&str`.

fn main() {
    let s = "a string".to_string();
    println!("{:?}", s.blue());     // <-- Invoking `.blue()` on `String`
}

output

Blue

By implementing Colorize for &str, .blue() can be invoked on String. If we implement Deref for an arbitrary struct in the same way String does, we should be able to reproduce the same behavior.

        :   // Continued from above example...
        :
impl Colorize for &str { }  // <-- `Colorize` only impl'd for `&str`.

use std::ops::Deref;

struct Foo { }              // <-- Arbitrary struct.

impl Deref for Foo {        // <-- Implements `Deref<Target = str>`.
    type Target = str;
    fn deref(&self) -> &Self::Target {
        "I am Foo!"
    }
}

fn main() {
    let f = Foo { };
    
    println!("{:?}", &*f);          // <-- Simple deref.
    println!("{:?}", f.blue());     // <-- Invoking `.blue()` on `Foo`.
}

output

"I am Foo!"
Blue

playground link

And it works; Foo above supports Colorize in the same way String does by implementing Deref with Target = str.

Foo doesn't actually support the Colorize trait at all - it just returns a &str when dereferenced, which the compiler takes into account when resolving the method call.

Related