Trait cannot access the member of a struct it implemented for?

pub trait Display {
    fn display_content(&self) {
        println!("No content to display");
        println!("{}", &self.content);
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub author: String,
    pub location: String,
    pub content:String,
}

impl Display for NewsArticle {}

This code gave error message:

error[E0609]: no field `content` on type `&Self`
  --> src/lib.rs:11:30
   |
8  | / pub trait Display {
9  | |     fn display_content(&self) {
10 | |         println!("No content to display");
11 | |         println!("{}", &self.content);
   | |                              ^^^^^^^
12 | |     }
13 | | }
   | |_- type parameter 'Self' declared here

Which means although Display is implemented for NewsArticle, it still cannot access its member. Am I doing something wrong or it is just like that?
If this is the case, this make the trait functionality a little bit limited. What if two structs have one member in common, and you want to use it in a trait in the exactly same way. Would you need write two implementations for each of them?
Thanks.

The implementation you wrote for display_content() is merely the Display's default implementation for that method; as such, it has to work for every possible type that could implement Display, not all of which will have a content field. If you want to access a field in a trait implementation, you need to put the method implementation inside the impl Display for NewsArticle block.

What if two structs have one member in common, and you want to use it in a trait in the exactly same way. Would you need write two implementations for each of them?

One option would be to give Display a content() method without a default implementation and to define fn content(&self) -> &str { self.content } in each impl Display for TYPE_NAME block. Then, you could use self.content() inside the default display_content() method.

5 Likes

Thanks, I tried your method, but it doesn't work:

impl Display for NewsArticle {
    pub fn content(&self) -> String {
        self.content
    }
}
impl Display for Tweet {
    pub fn content(&self) -> String {
        self.content
    }
}
pub trait Display {
    fn display_content(&self) {
        println!("No content to display");
        // println!("{}", &self.content);
        println!("{}", self.content());
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub author: String,
    pub location: String,
    pub content:String,
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

compile error:

error[E0407]: method `content` is not a member of trait `Display`
  --> src/lib.rs:50:5
   |
50 | /     fn content(&self) -> String {
51 | |         self.content
52 | |     }
   | |_____^ not a member of trait `Display`

error[E0407]: method `content` is not a member of trait `Display`
  --> src/lib.rs:55:5
   |
55 | /     fn content(&self) -> String {
56 | |         self.content
57 | |     }
   | |_____^ not a member of trait `Display`

error[E0599]: no method named `content` found for reference `&Self` in the current scope
  --> src/lib.rs:12:29
   |
12 |         println!("{}", self.content());
   |                             ^^^^^^^ method not found in `&Self`

Even it works, looks like it is more work than to implement twice the display_content() function for each of the structs.

You forgot to:

like this:

pub trait Display {
    fn content(&self) -> String;
    fn display_content(&self) {
        println!("No content to display");
        // println!("{}", &self.content);
        println!("{}", self.content());
    }
}
1 Like

Thanks, yeah, I guess this gonna work.

But I am just saying you will still need have implementations on both struct, instead of letting both share one implementation. This is a little lower than my expectation. Maybe I am seeking some other features.

A following up question here:

How did rust compiler treat a struct being a type of impl Summary?
if I write something like this:
impl Summary for Tweet {}
can Tweet be considered as a type of impl Summary?
I mean when I write a function to output impl Summary, i put Tweet there, like this:

fn foo() -> impl Summary {
    Tweet {
        ...
    }
}

Can I do that? Thanks.

impl Trait had two basic usages: as the start of an implementation of a trait for a type, and, exactly as you've written, as a "figure out what type goes here" when used as a parameter or return type (technically they're quite different, but the details don't matter much here).

So yes if you have impl Trait for Type somewhere, you can replace a parameter or return type use of Type with impl Trait, but it does mean you can only use the members of Trait on the value. This means you can (before compiling) replace the actual type with a different implementation of Type and know that everything will still work.

As an aside, if you want to be able to use different implementations at the same place at runtime, you instead need dyn Trait, but that needs to be behind some pointer like &dyn Trait or Box<dyn Trait>.

2 Likes

You are seeking OOP, basically. Mistake #6a.

Rust doesn't do OOP (at least it doesn't do Simula 67-style OOP) which leads to tons of topics like yours.

First people discover they can not use implementation inheritance, then they find out they couldn't access fields from traits, then realize that default implementation of trait is part of it's interface (not something private), then they hit issues with subclassing and so on.

All that comes from the desire to avoid learning Rust.

Instead of looking on how can you solve problems in Rust you try to find out how to write Java in Rust (or C++ in Rust or JavaScript in Rust or, heck, even OCamls in Rust or Haskell in Rust).

It works poorly. Rust is opinionated and it has it's own idiomatic approaches to real-world problems while the ability to write ๐“ข๐“ธ๐“ถ๐“ฎ๐“ž๐“ฝ๐“ฑ๐“ฎ๐“ป๐“›๐“ช๐“ท๐“ฐ๐“พ๐“ช๐“ฐ๐“ฎ in Rust is not even considered important to discuss, usually.

You can do that but for reasons outlined above it's rarely a good idea. Although sometimes yes, it's useful.

3 Likes

impl Trait isn't itself a type per se. It's used where types are also used in two places in stable Rust.


Argument position:

// Argument Position Impl Trait (APIT)
fn foo(s: impl Summary) { /* ... */ }

// Basically the same as the following, but you can't name the generic
fn foo<S: Summary>(s: impl Summary) { /* ... */ }

So APIT is basically like a generic parameter with a trait bound. You can pass in anything that meets the bound. Once you have implemented the trait:

impl Summary for Tweet {}

You can meet the : Summary bound. So you could pass a Tweet to foo. Any other type that implements Summary can be passed into foo, as well.


Return position:

// Return Position Impl Trait (RPIT):
fn bar() -> impl Summary { /* ... */ }

This functionality is more unique. It says, "bar is going to return some type that implements Summary -- but that's all the caller gets to know. The type is otherwise opaque." It's useful for a few reasons:

  • It's flexible for the writer of foo -- they can change the returned type without breaking downstream
  • There are types in Rust which are unnameable, such as
    • Closures
    • Compiler-generated Futures
    • APIT argument types

Because Tweet implements Summary, you could return a Tweet from bar.

However, note that an RPIT type is still an opaque alias for a single type. That is, you can't do something like

fn bar() -> impl Summary {
    if random() {
        Tweet::new("...");
    } else {
        Magazine::new("...");
    }
}

Rust is statically typed, so you can't have a "sometimes This, sometimes That" return type.

(Your RPIT can vary in type based on generic input types, though. e.g. fn quz<T>(v: Vec<T>) -> impl Iterator<Item=T> may return a different iterator type for every distinct T.)

So here RPIT is an opaque alias, not a type itself. You can't add methods or implement other traits for a return-position impl Trait, for example.


What if you do want to return different types that implement Summary, or otherwise treat multiple types that implement Summary as the same type? If you're trait is object safe (required for technical reasons), you can coerce any implementor of Summary into dyn Summary.

When you do this, the base type is erased. Instead you now have a dyn Summary, which is its own singular, distinct, concrete type. The compiler also supplies an implementation of Summary for dyn Summary. This type is dynamically sized (the runtime size is the size of whatever base type got erased), so it has no statically known size -- we say it is not Sized or that it is unsized. To be able to use unsized types, they have to be behind some sort of pointer, so you will mainly see things like Box<dyn Summary> for owned dyn objects and &dyn Summary for borrowed ones.

If you "own" the trait, you own the dyn Trait type too, and you can implement methods on it directly, implement other traits for it, etc.

That's a very brief introduction, but dyn Summary is the closest thing to an impl Summary that's actually type (for any implementor of Summary).

4 Likes

Thanks. You are exactly right!

I am seeking the OOP inheritance. But I didn't finish the Rust book yet, lets' see what else is down there.

I used C++ OOP before, but to be frank, not much stuff are shared between brothers in my program. So, maybe Rust got better solutions.

You wouldn't find OOP there, that's for sure.

Rust doesn't support OOP because it's dangerous.

Traditionally OOP is presented as a design which ties together three principles: encapsulation, inheritance, polymorphism.

Unfortunately these principles are fundamentally incompatible. Classic โ€œpick any two of threeโ€ situation.

The thing which makes all three compatible in some cases, Liskov substitution principle goes so far beyond anything compiler can verify it's not even funny.

And from experience with OOP-languages we know that not only compilers have trouble with it, people are not handling it well either.

That's why, ultimately, Rust have never implemented implementation inheritance (i was surprised to find out initial reasoning was different, but if it were possible to implement safely then they would have added it sooner or later).

No, butโ€ฆ

That's why Rust is staying with it's decision: while sometimes OOP is incredibly useful in practice you don't need it too often.

Where you really need it Rust usually uses macros (in particular derive macros are very helpful in cases which you discuss).

These are not as convenient as OOP, but they are safer and in practice you need them less that you may feel if you just reed OOP book.

2 Likes

Conveniently, there's a whole chapter about Object Oriented Programming Features of Rust - The Rust Programming Language.

OOP means many different things to different people. It's good to learn how to tease apart the different things that get lumped under its umbrella, and understand how Rust approaches the various goals that OOP also addresses.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.