Expected trait object dyn Trait, found struct Struct

Hi everyone,

This question has already been asked a lot of times, but I still can't make sense of the answers I've found (rust - expected trait object `dyn Responsability`, found type parameter `T` - Stack Overflow, https://www.reddit.com/r/rust/comments/2m98c1/why_cant_i_treat_this_struct_as_a_trait_it/, Trait objects and structs - #2 by alice)

I am trying to implement very simple polymorphism, that exists in any OOP language, by defining a "behaviour" and then having structs that implement this behaviour differently

But then, as I try to produce instance of a "dyn trait" object, I can't think of how to do it:

trait ClassNameEvaluator {
    fn evaluate_if_class_needed(&mut self, class_name: &str) -> anyhow::Result<bool> {
        return Ok(true);
    }
}

struct Dummy{}

impl ClassNameEvaluator for Dummy {}

struct LuaEvaluator<'a> {
    executor: Lua<'a>,
    luacode: &'a str
}

impl ClassNameEvaluator for LuaEvaluator<'_> {
    fn evaluate_if_class_needed(&mut self, class_name: &str) -> anyhow::Result<bool> {
        self.executor.set("classname", class_name);
        match self.executor.execute(self.luacode) {
            Ok(res) => anyhow::Ok(res),
            _ => Err(anyhow!("Error executing Lua code"))
        }
    }
}

and in the fn main:

    let mut evaluator: dyn ClassNameEvaluator =
        match cli.luaclassfilter.as_ref() {
            Some(code) => LuaEvaluator::new(code),
            None => dummy
        };

and expression in Some(code) branch, gives me

   |
89 |             Some(code) => LuaEvaluator::new(code),
   |                           ^^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn ClassNameEvaluator`, found struct `LuaEvaluator`
   |
   = note: expected trait object `dyn ClassNameEvaluator`
                    found struct `LuaEvaluator<'_>`

How should I go about it in a Rust-ish way? Thanks a lot!

1 Like

You can't hold a dyn Trait value by-value, because it's dynamically sized. You'll have to put it behind some indirection, e.g. Box (if you want to own it) or & (if you don't need to).

2 Likes

You can't have a bare trait object like that. It needs to be behind a pointer of some sort.

let mut evaluator: Box<dyn ClassNameEvaluator> =
        match cli.luaclassfilter.as_ref() {
            Some(code) => Box::new(LuaEvaluator::new(code)),
            None => Box::new(dummy),
        };

The error you're getting basically means "you're using a concrete struct but none of the coercion rules for changing into a trait object applied". And that's because they only apply to types that can contain a trait object like Box, Arc, and references

2 Likes

Ok, I think I needed some casting but it still does not help:

    let mut dummy = Dummy{};
    let mut evaluator: &mut dyn ClassNameEvaluator =
        match cli.luaclassfilter.as_ref() {
            Some(code) => &mut LuaEvaluator::new(code) as &mut dyn ClassNameEvaluator,
            None => &mut dummy
        };

this now complains about temporary value that is freed while still in use.... :(((

You'll need the Box here. You are creating the value, so you own it, you have to hold onto it if you want a reference to it. References in themselves don't cause values to stick around.

phew.. I need to wrap my head around this. Why allow for declaring function arguments as traits if one can not pass anything to such method??

ok, I will read this Traits: Defining Shared Behavior - The Rust Programming Language.. tomorrow :confounded:

Fine, now I can call my method with both implementations like this:

fn list_zip_contents(reader: impl Read + Seek, class_name_evaluator: &mut (impl ClassNameEvaluator + ?Sized)) -> anyhow::Result<()> {
...
}

    let mut dummy = Dummy{};
    let mut lua_evaluator = LuaEvaluator::new("return true");
    list_zip_contents(jarfile,  &mut lua_evaluator)?;
    list_zip_contents(jarfile,  &mut dummy)?;

all of that compiles and works. So now question is how to build an owned mut value from an Option

Ok, here's what actually works:

fn list_zip_contents(reader: impl Read + Seek, class_name_evaluator: &mut dyn ClassNameEvaluator) -> anyhow::Result<()> {...}


    let mut evaluator: Box<dyn ClassNameEvaluator> =
          match cli.luaclassfilter.as_ref() {
              Some(code) => Box::new(LuaEvaluator::new(code)),
              None => Box::new(Dummy{})
          };
    list_zip_contents(jarfile, evaluator.deref_mut())?;

You absolutely can; I don't understand what your new issue is.

Whatever it was it is resolved for now :slight_smile: my biggest issue is confusion and misconceptions, coming from my Java background mostly

1 Like

Continuing the discussion from Is Rust OOP? If not, what is it oriented to?:

exactly my case :joy: (from the replies to the thread where you have also commented)

So the things to know are:

  • The presence of a reference to a value necessarily means that someone, somewhere, owns that value.
  • References don't own values. You own things by value.
  • You can only own something if you know its static size at compile tome. This is not obvious, as this is just a current technical requirement in Rust, which may be lifted in the future.
  • dyn Trait is dynamically sized, so you have to put it behind indirection in order to own it.
  • It's possible to own stuff and put it behind indirection, but that's not what references are for. Owning heap-allocating smart pointers include Box (when you want a single owner) and Rc/Arc (when you want multiple, refcounted owners).
  • Therefore, when you create a trait object that you want to own, you have to put it in Box or Rc or Arc.
  • But if you already have a boxed or refcounted (ie. owned) trait object, you can pass a mere reference to it into another function.

That is, there is nothing special about your situation/error wrt. trait objects; ownership and borrowing rules apply in exactly the same way to trait objects as they apply to any other type. What tripped you up is that you wanted to create a dynamically-sized value on the stack, which is not possible.

2 Likes

Someone needs to go about writing "Rust misconceptions" book :slight_smile: I can see at least 3 audiences with their own pre-conceived expectations about "how programming works" approaching Rust and having different, but still quite typical, issues :

  1. people from managed runtimes/languages, like Java/C#
  2. people from dynamically typed languages, like node.js, Python, Ruby
  3. people from C/C++ background

I kinda knew about ownership and references, and have some scars from fighting the BC in the past, but never used traits yet, at least not directly, so yeah, that was an exercise :slight_smile:

I still do not get it.

Here is my test code:

use std::rc::Rc;

trait SomeTrait {
  fn hello(&self);
}

struct SomeStruct { }

impl SomeTrait for SomeStruct {
  fn hello(&self) { println!("hello"); }
}

fn do_hello(trait_obj : Rc<dyn SomeTrait>) {
  trait_obj.hello()
} 

fn main() {
  let a = Rc::new(SomeStruct{} );

  let b = Rc::clone(&a);
  do_hello( b );  // This compiles ok

  do_hello( (|| Rc::clone(&a))() ); // Weird, but compiles

  do_hello( a.clone() ); // still ok

  // do_hello( Rc::clone(&a) ); // Why is this an error?
}

After reading the replies above, I cannot explain why the three first calls to do_hello compile ok, but the fourth one does not. I am under the impression that Rc::clone(&a) is just an alternative syntax for a.clone(), so they should be semantically the same.

Can someone please explain what is going on here?

1 Like

In the first three cases, a clone of Rc<SomeStruct> happens, and is then coerced to Rc<dyn SomeTrait>. In the forth case, the compiler tries to figure out which Rc::<_>::clone you're trying to call and -- based on the expected argument type -- wrongly decides on Rc::<dyn SomeTrait>::clone. It works if you correct it.

The difference from a.clone() is the lack of a generic type variable to infer, and the difference with the other cases is how far inference decides to look before falling back to a's type (or not).

1 Like

Thanks for the sensible explanation.

My understanding is now that the code is not actually wrong, but this is just a shortcoming of the current type inference algorithm. So the compiler flags this as an error just to be on the safe side. But this may change as the compiler evolves.

Yep, there's so much instances of such behaviour in rust for newcomers))) I so often struggle on some code and juggle bits around based on the suggestions from error messages, until BOOM :exploding_head: it just compiles ))) and when compiled it mostly just works

Nice but spooky :smiley::smiley:

The code is not technically wrong, but if you manage to confuse the compiler with it, imagine what it does to human brains. I wouldn't say the compiler is at fault here. You should really try to write your code in a way that it's unambiguous to a programmer too – and this instance clearly isn't.

Don't try to be excessively "clever" with inference (or any other language construct). These compiler non-bugs are a gentle reminder that you may be trying too hard to impress fellow programmers or the Rust Gods.

The compiler isn't trying to second-guess you. If anything, this should be reassuring and not "spooky".

I work a lot with automated systems (let's not call them "AI") where medical decisions are being made based on a statistical model's opinion of a physiological measurement, such as blood pressure or ECG. Of course, measurements can be noisy, absent, or just downright wrong.

What should a model decide to do when it doesn't know if the measurement is correct? Send the patient to 25 different kinds of surgeries, just to be safe? Surely that would not be a splendid idea. It should just tell the doctor that it should see patient no. 1337 because his/her measurements look not quite OK.

The point is, it's vastly better for an automated system to have a well-calibrated sense of confidence, and bail out when it's obviously unsure, instead of trying hard to make a decision and screw up big time.

Inference coupled with coercions is hard. The compiler is intentionally not trying to see two steps into the future. Rust is protecting your life – instead of sending you to a potentially harmful surgery, it tells you to see a doctor.

I get what you are saying, and in general, I fully agree. Code should be clear and readable, unless you are participating in an obfuscated code contest.

However, this code is not trickery to confuse or impress anyone. While a.clone() may be the most straightforward way to write this, the problematic Rc::clone(&a) is the form recommended in the documentation of Rc. The reason for this recommendation is that it more clearly states that the cloning here applies to the Rc only, not the referenced object. This is, IMHO, a good recommendation, and there is just a small gap in the type inference implementation.