Why doesn't Rust allow fields in trait?

I want to implement function as following

struct BaseMaterial {
    sentense: String,
    word: String,
    //more fields need accessed in trait 'Say'
   ...
}
trait Say {
    fn say(&self) {
        self.translate(self.get_sentense());
        self.translate(self.get_word());
    }
    //getter method
    fn get_sentense(&self)-> &str;
    fn get_word(&self)-> &str;
    // more getter method
    ...

    // virtual function need be implement by sub-type
    fn translate<'a> (&self, original: &'a str)-> &'a str;
}

struct SayEnglish {
    material: BaseMaterial,
    ...
}
impl Say for SayEnglish {
    fn get_sentense(&self)-> &str {
        &self.material.sentense
    }
    fn get_word(&self)-> &str {
        &self.material.word
    }
    //more bridge method
    ...

    // implement virtual function
    fn translate<'a> (&self, original: &'a str)-> &'a str {
        //translate
        original
    }
}

But these code looks ugly because BaseMaterial and trait Say are coupling especially there are many fields need to access in trait. Is there a better way?

I am a newbie, in my opinon, 'BaseMaterial' and 'Say' should almost be one structure which like virtual class in C++. But I want to know how to implement this more gracefully in rust. Please help, thanks

If the RFC hasn't been touched in two years, that should tell you all you need to know.

A trait is not an interface. If you need access to a given field, you should consider using a getter method (which can be present in a trait). This is not only more idiomatic, but avoids needlessly exposing internal details.

1 Like

Thanks for reply.
I should have used getter method in the demo code, but the code looks ugly especially when there are many field need to access. please inspect my code and suggest a better method, thanks.

What do you mean by "far too ugly"? If you're concerned about the overhead of writing getters, you could almost certainly write a proc macro to do it for you.

Right now, you're trying to use a trait as an interface. Don't. You can define a trait that provided access to that field and have it be a supertrait to whatever you're trying to do. Beyond that and I'll need some actual code.

Basically, the code you've provided simply doesn't show the problem you're running into. With what you've shown, there is no reason a visibility modifier wouldn't suffice.

2 Likes

Thanks for reply.
I said that the code is ugly because when there are many BaseClass fields that need to be accessed in SomeTrait's method , BaseClass and SomeTrait are tightly coupled. In addition, all these access requirements need to be bridged by WorkClass.
From another point of view, BaseClass and SomeTrait should almost be in the one structure where its method can directly access its fields without being transmitted by WorkClass.

The struct names signals that you're trying to implement Java-style OO hierarchy, which doesn't works well with Rust. Neither struct nor trait are class. Traits are abstract behavior shared between types, and structs holds some state as its fields. Classes tends to mix both roles, sometimes more other roles too.

If the operation is tightly coupled with state, it's not a good idea to abstract it as a trait. Instead, you can make a top level struct which holds abstracted extensions.

struct Wrapper<T, Ext: Extension> {
    some_field: T,
    ext: Ext,
}

trait Extension {
    fn talk(&self) -> String;
}

impl<T: Copy, Ext: Extension> Wrapper<T, Ext> {
    fn need_access_field(&self) {
        self.some_field;
        println!("ext says {}", self.ext.talk());
    }
}
4 Likes

To give a perhaps more satisfying answer in addition to the previous, correct, albeit somewhat philosophical ones:

In Rust, data and behavior are separate, and the layout of every type is exactly and statically determined at compile time. You define fields in your struct once, and they stay there forever. This is better for both readability, safety, and performance, since the definition of a type isn't scattered all over the place, there's no need for a runtime to dynamically adjust the layout and size of a type, and unsafe code can exactly know and rely on the size of a type being constant.

If traits were allowed to define structs, all or most of this would break down. So, view it as a technical necessity.

7 Likes

@leafinsky

I think, this sums it up pretty nicely.

Basically, when you think about classes in programming languages like Java, it translates to

struct SMyClass { … }
trait TMyClass { … }
impl TMyClass for SMyClass { … }

and instantiating the class translates to

let my_class: Box<dyn TMyClass> =
    Box::new(SMyClass { … });

Inheritance boils down to

struct SMyClassB {
    super: Box<dyn TMyClass>,
    …
}

impl TMyClass for SMyClassB { … }
impl TMyClassB for SMyClassB { … }

and traits with fields are nothing, but classes, that enable multiple inheritance by being classes with some restrictions.

The primary question you should ask yourself is, "what does my program do?". Data structures and functions are simply a tool to help you organize your program, but none are required for your program to work. What I recommend is, instead of focusing on the way to the goal, you should focus on the goal itself and let abstractions arise naturally on your way to achieve the goal. Abstractions are only useful, if they serve a purpose, otherwise they add unnecessary complexity to your program.

4 Likes

Thank @H2CO3 and @Hyeonu for kindly reply.
I believe that Rust team must have sufficient reason to do so. But I still want to know how to implement the following feature in more graceful way.
the example refer to @Hyeonu 's reply :slight_smile:

struct BaseMaterial {
    sentense: String,
    word: String,
    //more fields need accessed in trait 'Say'
   ...
}
trait Say {
    fn say(&self) {
        self.translate(self.get_sentense());
        self.translate(self.get_word());
    }
    //getter method
    fn get_sentense(&self)-> &str;
    fn get_word(&self)-> &str;
    // more getter method
    ...

    // virtual function need be implement by sub-type
    fn translate<'a> (&self, original: &'a str)-> &'a str;
}

struct SayEnglish {
    material: BaseMaterial,
    ...
}
impl Say for SayEnglish {
    fn get_sentense(&self)-> &str {
        &self.material.sentense
    }
    fn get_word(&self)-> &str {
        &self.material.word
    }
    //more bridge method
    ...

    // implement virtual function
    fn translate<'a> (&self, original: &'a str)-> &'a str {
        //translate
        original
    }
}

please help, thanks

thanks for kindly reply.
My question about "why not rust allow fields in trait" is not challenge why rust not implement like virtual function, but try to understanding the cause why they do like this

I would implement it like this:

struct Material<T: Language> {
    sentense: String,
    word: String,
    language: T,
}

trait Language {
    fn translate(&self, original: &str) -> String;
}

impl<T: Language> Material<T> {
    fn say(&self) {
        self.language.translate(&self.sentense);
        self.language.translate(&self.word);
    }
}

struct English;

impl Language for English {
    fn translate(&self, original: &str) -> String {
        original.into()
    }
}
3 Likes

Got it. Thank you very much!