Generic problem: cannot access pub field of struct


#1

Hi,

I have a strange error and I’m not able to find any solution. I wrote a small example for the error:

trait Options {
    // ...
}

struct SomeOptions {
    pub option: String,
}

impl Options for SomeOptions {
   // ...
} 

trait OptionPrinter {
    fn print<O: Options>(&self, options : O);
}

struct SomeOptionPrinter;

impl OptionPrinter for SomeOptionPrinter {
    fn print<SomeOptions>(&self, options: SomeOptions)  {
        println!("Some option: {}", options.option); // <-- ERROR
    }
}

fn main() { 
    let printer = SomeOptionPrinter;
    printer.print(SomeOptions{option: "foo".to_string()});
}    

After compiling I get this error (I have marked the line above):

error: attempted access of field `option` on type `SomeOptions`, but no field with that name was found
         println!("Some option: {}", options.option);

Can anyone tell me what is wrong with this code?
(I’m using rustc 1.5.0)


#2

It’s not strange at all; your code, as written, doesn’t at all do what it suggests.

    fn print<SomeOptions>(&self, options: SomeOptions)

This does not mean that the type parameter to print is going to be the struct type SomeOptions; it just means you have a completely arbitrary type parameter which happens to be called SomeOptions. This is absolutely equivalent to:

impl OptionPrinter for SomeOptionPrinter {
    fn print<CouldBeAnything>(&self, options: CouldBeAnything)  {
        println!("Some option: {}", options.option);
    }
}

There’s no reason to expect a CouldBeAnything to have a field called option, so of course it doesn’t work. What’s more, this isn’t even compatible with the trait you’re trying to implement. The trait specifies that the first type parameter is bound by the trait Option.

When you’re dealing with generic type parameters in Rust, you can only perform operations supported by the listed bounds. So, in this case, you need to use a method defined by Option. I don’t know what exactly you’re trying to do here, but this is one way you could do it:

trait Options {
    fn get_option(&self) -> &str;
}

struct SomeOptions {
    pub option: String,
}

impl Options for SomeOptions {
    fn get_option(&self) -> &str {
        &self.option
    }
}

trait OptionPrinter {
    fn print<O: Options>(&self, options: O);
}

struct SomeOptionPrinter;

impl OptionPrinter for SomeOptionPrinter {
    fn print<O: Options>(&self, options: O)  {
        println!("Some option: {}", options.get_option());
    }
}

fn main() { 
    let printer = SomeOptionPrinter;
    printer.print(SomeOptions{ option: String::from("foo") });
}

(Aside: you should generally avoid .to_string() for constructing Strings; it’s inefficient as it goes through the formatting system instead of just directly constructing the result.)


#4

Okay, but in the code

impl OptionPrinter for SomeOptionPrinter {
    fn print<SomeOptions>(&self, options: SomeOptions)  {
...

the type of the paramter options is explicit given and the compiler can check whether the type has a field option.
If I use CouldBeAnything and CouldBeAnything has no field option then the compiler should stop with this error…
…so I still do not understand why this is an error…?


#5

And for example this code compiles:

trait Options {
    type O : Clone;
}

#[derive(Clone)]
struct SomeOptions {
    pub option: String,
}

impl Options for SomeOptions {
    type O = SomeOptions;
} 

trait OptionPrinter<O: Options> {
    fn print(&self, options : O::O);
}

struct SomeOptionPrinter;

impl OptionPrinter<SomeOptions> for SomeOptionPrinter {
    fn print(&self, options: SomeOptions)  {
        println!("Some option: {}", options.option);
    }
}

fn main() { 
    let printer = SomeOptionPrinter;
    printer.print(SomeOptions{option: String::from("foo")});
}       

But I don’t want a generic trait - only the method should be generic…

(How do I post syntax highlighted code in this forum? I can’t find any documentation…)


#6

No, it isn’t. You’ve just written the code in such a way that it looks like it has.

The <SomeOptions> part is introducing a generic type parameter, whose name is shadowing the actual SomeOptions struct type. You absolutely cannot, in this context, require that options is actually of type SomeOptions.

As I said above, that name is just confusing matters. Change it to T or CouldBeAnything; it is not doing what you think it is.


#7

Then, as I said, you need to add something to the trait to access the field. You can’t accept a generic type, and then do something that depends on knowing what the type is.

Because, in this specific case, options really is a SomeOptions, and the compiler can prove it.

```rust
fn rust_code() {}
```
```

#8

Hi biggx,

you’re implementing a trait for a specific type (here: OptionPrinter), so the only thing that the compiler cares about is the trait definition:

    trait OptionPrinter {
        fn print<O: Options>(&self, options : O);
    }

In your second example you’ve specified an associated type. Here you tell the compiler explicitly that the type is SomeOptions.

Think about it this way: You can implement the trait for any type: ints, floats, structs, etc. And not all of them do have fields like a struct has. But you can define functions (methods) for all of them. And this is what the compiler can guarantee: that you have defined all the functions that are demanded by a specific trait.


#9

Okay it makes sense that the type is not “explicit given” respectively that the compiler only cares about the trait definition … because I can write this:

fn do_print<T: OptionPrinter, O: Options>(printer: T, options: O) {
    printer.print(options);
}

fn main() { 
    let printer = SomeOptionPrinter;
    let options = CouldBeAnything; // ... that impl Options
    do_print(printer, options);
}  

…and this would not work with my code…