'Generic function that returns different types based on the value of an argument

I have a struct which holds registers. I want my read_register function to return a u8 for Register::V0 and Register::V1 but a u16 for Register::V2 and Register::V3. I'm not sure how to make the function generic over the return type. I'm getting the error match arms have incompatible types which does make sense because the types are different.

struct Registers {
    v0: u8,
    v1: u8,
    v2: u16,
    v3: u16,
}

enum Register {
    V0,
    V1,
    V2,
    V3,
}

impl Registers {
    fn read_register<T>(&self, register: Register) -> T {
        match register {
            Register::V0 => self.v0,
            Register::V1 => self.v1,
            Register::V2 => self.v2,
            Register::V3 => self.v3,
        }
    }
}


Solution 1:[1]

You can't.

Sorry, but there's just no way of doing this in Rust. You'd need dependent types, which Rust doesn't have. You could perhaps return an instance of an enum that just contains two variants (one for each type). Or you could accept one callback for each "path" and let the caller decide how to resolve the problem.

fn read_register<FnU8, FnU16, R>(
    &self,
    register: Register,
    with_u8: FnU8,
    with_u16: FnU16,
) -> R
where
    FnU8: FnOnce(u8) -> R,
    FnU16: FnOnce(u16) -> R,
{
    match register {
        Register::V0 => with_u8(self.v0),
        Register::V1 => with_u8(self.v1),
        Register::V2 => with_u16(self.v2),
        Register::V3 => with_u16(self.v3),
    }
}

Solution 2:[2]

With [associated types][1] you can do a few things that would require path dependent types. In this case, like the following code:

pub struct Registers {
    pub v0: u8,
    pub v1: u8,
    pub v2: u16,
    pub v3: u16,
}
pub trait GetRegister {
    // this associated type is used to simulate path dependent types.
    type ReturnType;

    fn apply(&self, register: &Registers) -> Self::ReturnType;
}

pub struct GetRegister1;
impl GetRegister for GetRegister1 {
    type ReturnType = u8;

    fn apply(&self, register: &Registers) -> Self::ReturnType {
        register.v1
    }
}
pub struct GetRegister3;
impl GetRegister for GetRegister3 {
    type ReturnType = u16;

    fn apply(&self, register: &Registers) -> Self::ReturnType {
        register.v3
    }
}

impl Registers {
    pub fn fetch_register<A,B>(&self, fetcher: A) -> B
        where A : GetRegister + GetRegister<ReturnType = B> {
        fetcher.apply(self)
    }
}
#[cfg(test)]
mod tests {
    use serde_json::*;

    use super::*;

    #[test]
    pub fn fetch_registers() {
        let registers = Registers {
            v0: 1,
            v1: 2,
            v2: 3,
            v3: 4
        };

        let v1 : u8 = registers.fetch_register(GetRegister1);
        let v2 : u16 = registers.fetch_register(GetRegister3);
        assert_eq!(2, v1);
        assert_eq!(4, v2);
    }
}

(I'm uncertain if this method is a good solution for something this small, though). [1]: https://doc.rust-lang.org/rust-by-example/generics/assoc_items/types.html

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DK.
Solution 2 Jan