3
votes

(Instruction decode in PDP emulator)

I have a huge match set where each arm returns a function pointer and a name. Here is an extract

         match (inst & 0o170000) >> 12 {
            0o00 => match (inst & 0o007700) >> 6 {
                0o00 => match inst & 0o77 {
                    00 => (Cpu::halt, "halt"), 
                    01 => (Cpu::halt, "wait"),
                    02 => (Cpu::halt, "rti"),
                    03 => (Cpu::halt, "bpt"),
                    04 => (Cpu::halt, "iot"),

every arm of these matches says (Cpu::halt,"xxx"). This happily compiles. But of course I want real functions in there so I changed the first one.

         match (inst & 0o170000) >> 12 {
            0o00 => match (inst & 0o007700) >> 6 {
                0o00 => match inst & 0o77 {
                    00 => (Cpu::mov, "halt"), 
                    01 => (Cpu::halt, "wait"),
                    02 => (Cpu::halt, "rti"),
                    03 => (Cpu::halt, "bpt"),
                    04 => (Cpu::halt, "iot"),

Both halt and mov have the same signatures

impl Cpu{
    pub fn halt(&mut self, z:Word)->Result<(), Exception>{Ok(())}

    pub fn mov(&mut self, z:Word) ->Result<(), Exception>{
        let (mut ss,mut dd) = self.decode_ssdd(z, false)?;
        let t = self.fetch_word(&mut ss)?;
        self.psw &= !statusflags::PS_V;
        self.set_status(t);
        self.store_word(&mut dd, t)?;
        Ok(())
    }
}

but rustc then complains

error[E0308]: `match` arms have incompatible types
  --> src\cpu.rs:83:31
   |
81 |                       0o00 => match inst & 0o77 {
   |  _____________________________-
82 | |                         00 => (Cpu::mov, "halt"), 
   | |                               ------------------ this is found to be of type `(for<'r> fn(&'r mut cpu::Cpu, u16) -> std::result::Result<(), common::Exception> {instructions::<impl cpu::Cpu>::mov}, &str)`
83 | |                         01 => (Cpu::halt, "wait"),
   | |                               ^^^^^^^^^^^^^^^^^^^ expected fn item, found a different fn item
84 | |                         02 => (Cpu::halt, "rti"),
...  |
90 | |                         _ => unreachable!(),
91 | |                     },
   | |_____________________- `match` arms have incompatible types
   |
   = note: expected type `(for<'r> fn(&'r mut cpu::Cpu, _) -> std::result::Result<_, _> {instructions::<impl cpu::Cpu>::mov}, &str)`
             found tuple `(for<'r> fn(&'r mut cpu::Cpu, _) -> std::result::Result<_, _> {instructions::<impl cpu::Cpu>::halt}, &'static str)`

the essential part of the error seems to be the last 2 lines that says the difference between the tuples it found is that one is (fn, &str) and the other is (fn, &'static str). And yet they are identical except the function name.

I also note the the earlier error says "expected fn item, found a different fn item" but thats not what the last 2 lines say.

1

1 Answers

3
votes

Consider this simplified case:

pub struct Exception{}

pub struct Cpu {}

impl Cpu{
    pub fn halt(&mut self)->Result<(), Exception>{Ok(())}
    pub fn mov(&mut self) ->Result<(), Exception>{Ok(())}
}

Then this compiles OK:

pub fn foo(cpu:&mut Cpu, i:i32) -> Result<(), Exception> {
    let f = match i {
        0 => Cpu::halt,
        _ => Cpu::mov,
    };
    f(cpu)
}

But this generates the same error that you see

pub fn bar(cpu:&mut Cpu, i:i32) -> Result<(), Exception> {
    let f = match i {
        0 => (Cpu::halt,"halt"),
        _ => (Cpu::mov,"mov"),
    };
    f.0(cpu)
}

It seems like in the bar case the compiler is unable to deduce the type for the tuple returned from the match. We can find out what type the compiler is deducing for f in the foo case by adding q:() = f; and looking at the error message. It is

17 |     let q:() = f;
   |           --   ^ expected `()`, found fn pointer
   |           |
   |           expected due to this
   |
   = note: expected unit type `()`
             found fn pointer `for<'r> fn(&'r mut Cpu) -> std::result::Result<(), Exception>`

We can then explicitly state that type for the tuple to get bar to compile (but we can remove the for<'r> ... 'r bit)

pub fn bar(cpu:&mut Cpu, i:i32) -> Result<(), Exception> {
    let f: (fn(&mut Cpu) -> Result<(), Exception>, &str) = match i {
        0 => (Cpu::halt, "halt"),
        _ => (Cpu::mov, "mov"),
    };
    f.0(cpu)
}

I'd personally use a type statement to get rid of the noise

type CpuFn = fn(&mut Cpu) -> Result<(), Exception>;
pub fn bar(cpu:&mut Cpu, i:i32) -> Result<(), Exception> {
    let f: (CpuFn, &str) = match i {
        0 => (Cpu::halt, "halt"),
        _ => (Cpu::mov, "mov"),
    };
    f.0(cpu)
}

You can see a complete working version here


ASIDE:

It is worth noting that using the q:() = Cpu::halt trick we see that the type of Cpu::halt is fn item for<'r> fn(&'r mut Cpu) -> Result<(), Exception> {Cpu::halt} which is not the same as that returned from the match in foo. The compiler is combining two branches with types

fn(&mut Cpu) -> Result<(), Exception> {Cpu::halt}

and

fn(&mut Cpu) -> Result<(), Exception> {Cpu::mov}

into a common type.

fn(&mut Cpu) -> Result<(), Exception>

but is unwilling to combine the types

(fn(&mut Cpu) -> Result<(), Exception> {Cpu::halt}, &str)
(fn(&mut Cpu) -> Result<(), Exception> {Cpu::mov}, &str)

into

(fn(&mut Cpu) -> Result<(), Exception>, &str)

I can't find any information on what conversions are performed on the return values of the match branches in general, nor can I find any information on the difference between fn item and fn pointer types in rust.