Why doesn’t Rust allow fields in trait? II

Hello Every Rustacean, I'm a newbee and I have a question for rust's polymorphism.

Here is the original post: Why doesn't Rust allow fields in trait? .
But If I want to access struct's field from trait's funciton, How could I achieve that?
For example: the trait's fn(auto_detect) will use the sentence to check if the language is right.

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

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

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()
    }

    fn auto_detect(&self) -> bool {
        // match (self.word)
        todo!()
    }
}

Nohow :face_with_diagonal_mouth:

Traits cannot describe fields. Try to put a method for accessing the field to your trait.

Your code is rather confusing - when you impl Language for English, you tell the compiler that Self is English, and you're implementing the methods of the trait Language.

If that was what you were doing, then auto_detect's implementation for English could use the fields on English to do its job.

However, in the code you've written, English doesn't have any fields at all; instead, it's contained in struct Material, and there is no way for a member of Material to "reach out" and find its peer members.

You could make this work by pushing word and/or sentense into struct English, which would give impl Language for English access to them, since they're now fields of English. You could also change auto_detect's signature, to take in the sentence and/or the word by reference (like you do for translate, so that it can look at what it's been given.

4 Likes

I don't understand what you're trying to do in your code, could you explain the goal of your program using words?

I imagine what Language::translate does is take a string and translate it (if that's the case, it should probably return a Result, since not all strings are valid sentences in a language).
I'm not sure what auto_detect is supossed to be do here, what it is supposed to detect?

By the looks of the last item, impl Language for English, you're wanting to use self as if it was of the type Material, since word is a field of it and not English. Maybe what you want is to implement Language for a Material<English>. I don't think that's the best way to structure a translation system, but go ahead.

I know it's a little confusing. Let me explain a little: Assuming English is not only a linguistic thing, but an essential material with words & sentences. English = Material<T: Language> itself.

You could see English is a struct , and you can add fields and refrence in the struct.

trait Language {
    fn get_word(&self) -> String;
    fn translate(&self, original: &str);
    fn auto_detect(&self) -> bool;  // a way to check language is legal from material
}

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

impl<T: Language> Material<T> {
    fn say(&self) {
        if self.language.auto_detect() {
            println!("Ops...");
        }
        self.language.translate(&self.sentence);
        self.language.translate(&self.word);
    }
}

// Arabic, Chinese, English, French, Japanese, Korean, Russian ...
struct English {
    // ...
} // is something materiallanguage

impl Language for English {
    fn get_word(&self) -> String {
        todo!()
    }

    fn translate(&self, original: &str) {
        println!("translating [{}] ", original);
    }

    // fixme: 1. you could pass material's fields here, but if there are dozens of fields in material,it's verbose to  pass all
    // fixme: 2. you could imply get_words
    fn auto_detect(&self) -> bool {
        // fixme:: how could I get material's fields directly using ref or other things as you like
        // match self.word + self.sentences { String { .. } => true }
        todo!()
    }
}

fn main() {
    let material =  Material {
        sentence: "foo bar".to_string(),
        word: "foobar".to_string(),
        language: English {},
    };
    material.say();
}

In my ambigious opion: Trait is "autisitic", like static things in other language(python, java)?

auto_detect is a fn to check language is legal for premise.

But English is a struct (like Material<T: Language>), If there're (A, B, C, D, E, F) fields in Matrial, and auto_detect want to check all these fields, is there some idiomatic way to achieve that using reference except passing the arguments to auto_detect nor add a method in trait.

Your problem has nothing to do with traits, just ownership.
Your functions seem stringly typed, perhaps even println!-ly typed. This could be improved by using more structs. In the case of string output, taking &mut impl Write may be appropriate.

You can solve almost any (ownership) problem with indirections structs!
Playground

5 Likes

Let's break it down a bit:

struct Material<T: Language> {
    language: T,
}

This says Material owns a generic type T which implements Language. There is nothing about this construct which claims that T knows anything about Material, which is what your comments in the auto_detect implementation imply.

If you need the T to know about Material, you need to invert the structure (therefore Material will not be able to know anything about T):

struct English {
    material: Material,
}

Now English can trivially gain access to fields of the Material that it owns. However, this is not what you want. It's just illustrative of the problem with your question.

You can adjust the method arguments on your trait to take a reference to a Material<T> to get this to work. It isn't pretty, and you are probably going to run into borrow checker problems before long, because this can only really work with shared borrows: Rust Playground

Ultimately, I more or less agree with other commenters who are saying you have a data model problem. Because this looks like a data model problem.

3 Likes

I think what you are seeking is implementation inheritance. Rust doesn't do that. Mistake 6a.

When people are asking could you explain the goal of your program using words they are asking about layman words.

Not explanation about how would you write that task in Python, C++ or JavaScript. But what actual task we are trying to solve here.

Because Rust doesn't do implementation inheritance, doesn't like “soup of pointers” pointers designs, etc it's usually good idea to understand what you are actually trying to implement before rushing with traits and structs.

I suspect the problem runs deeper and TC doesn't even understand what words “data model” even mean.

I suspect @ geniusnut just tries to find a way to write Python in Rust.

It's not, strictly speaking, impossible, but for that to happen you have to understand data model of Python, Rust's requirements for data models and the way how to map one into another.

Practically speaking it means writing Python in Rust is not possible: by the time you have required knowledge to actually do that that... you no longer need that ability.

2 Likes

Rust cannot express (by design) that English inherits from Material<English> (in part because Rust doesn't do inheritance at all).

Given the design you've shown so far, I'd make auto_detect take a parameter, the way translate does, and have it return "yes, this is in my language", or "no, it's not my language" as a result.

1 Like

Thanks for
parasyte's sharp way to handle the question.
@doublequartz's coding philosophy like "ashes to ashes".
@VorfeedCanal advice like a mentor.
Thank you all, and others too.

BTW I have a question for @VorfeedCanal. Should I holding this point: RUST is something pure functional parts(traits) that have the ability to share datas between each other by borrowing, and struct is the composer for the data and the traits?

Rust provides interface inheritance but not implementation inheritance (sidenote: I was surprised to find out implementation inheritance wasn't implemented initially mostly because of technical issues, but it was never brought in because it just doesn't fit in the Rust's “safety” story).

SOLID depends on LSP and LSP is thoroughly non-local: if A is B then A is always B and because, in practice, no one is able to keep all that inheritance strictly correct in the LSP sense that turns implementation inheritance based designs into a pile of hacks sooner or later.

Rust uses variant of Hindley–Milner type system which doesn't play well with OOP. In fact it can be seen even in C++: it's object slicing problem is related to that issue.

But because C++ was born as C with classes it have to deal with it, somehow — and it deals with it in a typical C++ fashion: “you have to hold it right, and if you don't then it's your problem”.

Rust couldn't accept such answer thus it just refuses to deal with implementation inheritance. Macros are often used as an alternative.

This works, but means your designs where implementation is not done in one place but is spread over many classes couldn't be implemented in Rust. At least not easily: if you would implement such things manually (like GObject does in C) then it works, but it's neither easy nor supported.

5 Likes

You're like a custom technical wiki for me.
Thank you again.

As an aside, do note that you can have multiple impl blocks for a single struct or enum, and you can spread those across multiple child modules of the one that defines the struct or enum`. So while you can't spread implementation easily across multiple classes, you can spread the implementation across multiple files to make code easier to review and to read.