Const generics, storing fn with const variables in struct

Edit: I've clarified this question a bit more in this post below. I think that I'm trying to store a fn which takes a struct with a const variable so that I can access that variable inside the fn. From a language perspective, I feel like this is a reasonable thing to want to do with const generics, would love if somebody could help me understand why or why not.


Hi all, I'm having some trouble with const generics. Not sure if it's just me or it's not available yet.

I'm trying to figure out the Fn signature for a boxed closure which would take a struct with a const variable. It seems to work fine when using a const variable on a function defn like: fn foo<const T: MyStructType>(bar: MyStruct<T>).

#![allow(incomplete_features)]
#![feature(const_generics)]

#[derive(PartialEq, Eq)]
pub enum VehicleType {
    Car,
    Boat,
}

pub struct Vehicle<const V: VehicleType> {
    wheels: u32
}

pub struct World {
    compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0412]: cannot find type `A` in this scope
  --> src/lib.rs:15:43
   |
14 | pub struct World {
   |                 - help: you might be missing a type parameter: `<A>`
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                           ^ not found in this scope

error[E0412]: cannot find type `B` in this scope
  --> src/lib.rs:15:55
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                                       ^ not found in this scope
   |
help: there is an enum variant `unicode_bidi::BidiClass::B`; try using the variant's enum
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<unicode_bidi::BidiClass>)>,
   |                                                       ^^^^^^^^^^^^^^^^^^^^^^^
help: you might be missing a type parameter
   |
14 | pub struct World<B> {
   |                 ^^^

error[E0107]: wrong number of const arguments: expected 1, found 0
  --> src/lib.rs:15:35
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                   ^^^^^^^^^^ expected 1 const argument

error[E0107]: wrong number of type arguments: expected 0, found 1
  --> src/lib.rs:15:43
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                           ^ unexpected type argument

error[E0107]: wrong number of const arguments: expected 1, found 0
  --> src/lib.rs:15:47
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                               ^^^^^^^^^^ expected 1 const argument

error[E0107]: wrong number of type arguments: expected 0, found 1
  --> src/lib.rs:15:55
   |
15 |     compare_transport: Box<dyn Fn(Vehicle<A>, Vehicle<B>)>,
   |                                                       ^ unexpected type argument

error: aborting due to 6 previous errors

Some errors have detailed explanations: E0107, E0412.
For more information about an error, try `rustc --explain E0107`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Is it possible you didn't define the consts A and B?

const A: VehicleType = VehicleType::Car;
const B: VehicleType = VehicleType::Boat;

hmmm... what if I want A and B to be any VehicleType?

I think you would have to wrap them with an enum, but I'm not sure. You can't put them behind a dyn because they are not traits. It seems like each Vehicle<_> is a type of it's own.

#![allow(incomplete_features)]
#![feature(const_generics)]

#[derive(PartialEq, Eq)]
pub enum VehicleType {
    Car,
    Boat,
}

pub struct GenericVehicle<const V: VehicleType> {
    wheels: u32
}

const A: VehicleType = VehicleType::Car;
const B: VehicleType = VehicleType::Boat;

type Car = GenericVehicle<A>;
type Boat = GenericVehicle<B>;

enum Vehicle {
    Car(Car),
    Boat(Boat),
}

pub struct World {
    compare_transport: Box<dyn Fn(Vehicle, Vehicle)>,
}

I guess I'd be sad if this is the best case. I think there wouldn't be much point anymore in using const generics then; I could just use enums, or enums wrapping structs without const params. Thanks for taking a look!

1 Like

Just wanted to clarify my question a little bit. I think that I'm trying to store a fn which takes a struct with a const variable so that I can access that variable inside the fn.

The code below doesn't work as written. My feeling is that syntax changes to Fn sig won't work either, but would love to be proven wrong. From a language perspective, I feel like this is a reasonable thing to want to do with const generics, would love if somebody could help me understand why or why not.
playground

#![feature(const_generics)]

#[derive(PartialEq, Eq)]
pub enum VehicleType {
    Car,
    Boat,
}

pub struct Vehicle<const V: VehicleType> { }

pub struct World {
    compare_transport: Box<dyn Fn(Vehicle<const V1>, Vehicle<const V2>) -> bool>,
}

#[cfg(test)]
mod test {
    use super::*;
    
    #[test]
    fn test() {
        use VehicleType::*;
        
        let hatchback: Vehicle<{Car}> = Vehicle {};
        let schooner: Vehicle<{Boat}> = Vehicle {};
        
        let world = World {
            compare_transport: Box::new(|_a: Vehicle<V1>, _b: Vehicle<V2>| {
                V1 == V2
            }),
        };
        
        assert_eq!((world.compare_transport)(hatchback, schooner), true);
    }
}

Each time you create a Vehicle<V> with a different value of V you are going to end up with a different type. However these types are necessarily related in any way to each other which is why you are running into issues. In order to write the correct function signature for compare_transport you need to find a type that can represent any of the vehicle types that can be created. I'd recommend creating the trait AnyVehicle that can be used to relate all of the types together:

pub trait AnyVehicle {
    fn get_type(&self) -> VehicleType;
}

impl<const V: VehicleType> AnyVehicle for Vehicle<V> {
    fn get_type(&self) -> VehicleType { V }
}

Then, you can change the definition of compare_transport to use this new type:

pub struct World {
    compare_transport: Box<dyn Fn(&dyn AnyVehicle, &dyn AnyVehicle) -> bool>,
}

I have copied your code to this playground.

Edit:

Also, as an aside you can't use a generic function/closure here since you are storing the function inside of World. A generic function expand out to multiple actual functions. For example:

fn compare_trans<const A: VehicleType, const B: VehicleType>(_a: &Vehicle<A>, _b: &Vehicle<B>) -> bool {
    A == B
}

is actually several functions: compare_trans::<Car, Car>, compare_trans::<Car, Boat>, ... etc. This is why the above example uses dynamic dispatch on a trait.

Your post makes sense; I guess between this post and @naim 's post, I'll have to reach for either traits or enums. Perhaps that means const generics won't provide that much benefit in this context.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.