Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling specific trait implementation on a non-generic struct

Tags:

rust

traits

I have a non-generic struct that implements a generic trait. When I call a function on the struct, I get the following error:

error[E0282]: unable to infer enough type information about `_`
  --> src/main.rs:35:18
   |
35 |     cpu.debugger.attach();
   |                  ^^^^^^ cannot infer type for `_`
   |
   = note: type annotations or generic parameter binding required

I've tried adding type annotations and generic parameter bindings, but I'm obviously doing something wrong; I still can't get it to compile. I have similar code elsewhere with a generic struct that works, presumably because the generic-bounds shared by the struct and trait impl allow the compiler to infer the actual method implementation to call.

The best way to illustrate the issue is with a reduced example:

struct Cpu<M: Memory, D: Debugger<M>> {
    mem: M,
    debugger: D,
}

impl<M: Memory, D: Debugger<M>> Cpu<M, D> {
    fn new(mem: M, debugger: D) -> Self {
        Cpu {
            mem: mem,
            debugger: debugger,
        }
    }
}

trait Memory {}

struct SimpleMemory;

impl Memory for SimpleMemory {}

trait Debugger<M: Memory> {
    fn attach(&mut self) {}
    fn step(mem: &M) {}
}

struct NoOpDebugger;

impl<M: Memory> Debugger<M> for NoOpDebugger {}

fn main() {
    let mut cpu = Cpu::new(SimpleMemory, NoOpDebugger);
    cpu.debugger.attach(); // <-- cannot infer type for `_`
}

Please excuse the poor title, but it's the best way I know how to describe the problem.

like image 584
w.brian Avatar asked Oct 02 '16 01:10

w.brian


People also ask

What are the advantages of generics over trait objects?

Generics have two major advantages over trait objects: Speed. When the compiler generates machine code for a generic function, it knows which types it's working with, so it knows at that time which writemethod to call. No need for dynamic dispatch.

What is a trait in programming?

A trait is a feature that any given type may or may not support. Think of a trait as a type capability. Rule: For trait methods to be accessible, the trait itself must be in scope! Otherwise, all of its methods are hidden. #![allow(unused)] fn main() { let mut buf: Vec<u8> = vec![]; buf.write_all(b"hello!")?; // ERR: no method named write_all }

What is a trait in C++?

Using Traits A trait is a feature that any given type may or may not support. Think of a trait as a type capability. Rule: For trait methods to be accessible, the trait itself must be in scope! Otherwise, all of its methods are hidden. #![allow(unused)] fn main() { let mut buf: Vec<u8> = vec![]; buf.write_all(b"hello!")?;

What is a generic function?

Intro to Generics A generic function or type can be used with values of many different types.


Video Answer


1 Answers

You have several options.

  1. You can specify on which specific trait you want to invoke the attach method.

    fn main() {
        let mut cpu = Cpu::new(SimpleMemory, NoOpDebugger);
        Debugger::<SimpleMemory>::attach(&mut cpu.debugger);
    }
    

    or

    fn main() {
        let mut cpu = Cpu::new(SimpleMemory, NoOpDebugger);
        <NoOpDebugger as Debugger<SimpleMemory>>::attach(&mut cpu.debugger);
    }
    
  2. You can move the attach method to a supertrait that is not generic.

    trait DebuggerBase {
        fn attach(&mut self) {}
    }
    
    trait Debugger<M: Memory>: DebuggerBase {
        fn step(mem: &M) {}
    }
    
    impl DebuggerBase for NoOpDebugger {}
    impl<M: Memory> Debugger<M> for NoOpDebugger {}
    
  3. You can add a PhantomData member to NoOpDebugger and make NoOpDebugger itself generic, so that each NoOpDebugger<M> only implements Debugger<M> for the same M. In your example, the M for NoOpDebugger will be inferred from the call to Cpu::new.

    use std::marker::PhantomData;
    
    struct NoOpDebugger<M>(PhantomData<M>);
    
    impl<M: Memory> Debugger<M> for NoOpDebugger<M> {}
    
    fn main() {
        let mut cpu = Cpu::new(SimpleMemory, NoOpDebugger(PhantomData));
        cpu.debugger.attach();
    }
    
  4. If the implementations of Debugger don't depend on M, and if you don't use Debugger as a trait object, then you can move the type parameter to the methods that need it and omit it on the methods that don't need it.

    trait Debugger {
        fn attach(&mut self) {}
        fn step<M: Memory>(mem: &M) {}
    }
    
like image 116
Francis Gagné Avatar answered Oct 01 '22 22:10

Francis Gagné