Traits override native functions?

So… not sure exactly what I’m asking here… but… isn’t this kinda funky?

Result of the below is “nope - 42 is nothing!”

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f53fa5c159dff6160e714b3b56128d2e

trait Foo {
    fn is_some(&self) -> bool;
    fn unwrap(self) -> u32;
}

impl Foo for Option<u32> {
    fn is_some(&self) -> bool {
        !self.is_some()
    }
    
    fn unwrap(self) -> u32 {
        self.unwrap()
    }
}

fn handle_foo<T: Foo>(foo:T) {
    match foo.is_some() {
        true => println!("yep - it's something!"),
        false => println!("nope - {} is nothing!", foo.unwrap()),
    } 
}

fn main() {
    handle_foo(Some(42u32));
}

And it gets weirder: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7128e665111fee9c18457abe42bf61df

Is there a good writeup somewhere about building intuition for which functions a trait sees, esp if it has conflicts with the native functions on the struct?

If the concrete type is known (or inferred), i.e. Option<i32> then methods on that type will be preferred. If not, like in a generic function, you can only use methods from trait.

1 Like

If I am doing something like this, I think it would be a good idea to differentiate by provide a prefix/suffix to make it clearer which version is being called, yeah?

Yes, and you can also use the UFCS to show that you are calling the trait method.

1 Like

If you want a lower-level view, the Guide to Rustc Development has this section:
https://rust-lang.github.io/rustc-guide/method-lookup.html

1 Like

Note that the trait’s versions of unwrap and is_some will only be called if your Foo trait is in scope. If this were in a different module, you would need to import Foo to get this behavior, otherwise the intrinsic implementations will be used.

2 Likes

Good point!

Seems like all the more reason to add prefixes/suffixes - since that wouldn’t be caught at compile-time…

Rust doesn’t have classes with inheritance, so overriding a function is not possible.

handle_foo is generic over T, which must implement Foo, so Foo::is_some is called instead of Option::is_some. But if you write Some(42u32).is_some(), then Option::is_some is called.

If you also had a trait Bar : Foo with the same methods and implementations, and changed the signature to fn handle_bar<T: Bar>(foo: T), then Rust would complain:

error[E0034]: multiple applicable items in scope
2 Likes