Why cant an enum be used as a Generic Constant?

I’m writing some generic math code to experiment with deterministic algorithms. The main things I want to do are:

  1. Specify the Storage Type, Scaling Type, Prefix and Units,
  2. Retain a maximum storage size for the struct as the same size of Storage Type,
  3. Use static dispatch only.

The interface I want is:

Angle<i16, f32, Prefix::Micro, AngularUnit::Gradians>

Unfortunately it seems you cant use enums as constants for generic arguments. The closes I can get is:

Angle<i16, f32, { Prefix::Micro as i8 }, { AngularUnit::Gradians as i8 }>

The following size test is must (and does) pass:

assert_eq!(size_of::<Angle<i16, f32, { Prefix::Micro as i8 }, { AngularUnit::Gradians as i8 }>>(), 2 );

The important bits of the implementation looks like this:

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Angle<
	StorageType: Number,
	ScalingType: Float,
	const STORAGE_PREFIX: i8,
	const STORAGE_UNIT: i8,
> {
	value: StorageType,
	scalar: PhantomData<ScalingType>,
}

impl<
	StorageType: Number,
	ScalingType: Float + From<f32> + From<StorageType> + Into<StorageType>,
	const STORAGE_PREFIX: i8,
	const STORAGE_UNIT: i8,
> Angle<StorageType, ScalingType, STORAGE_PREFIX, STORAGE_UNIT>
{
	pub fn new<InitType: Number + Into<StorageType> + Into<ScalingType>>(
		value: InitType, prefix: Prefix, unit: AngularUnit,
	) -> Self {
		if prefix == Prefix::from(STORAGE_PREFIX) && unit == AngularUnit::from(STORAGE_UNIT) {
			Self { value: value.into(), scalar: PhantomData }
		} else {
			let p = prefix.into();
			let s = Prefix::from(STORAGE_PREFIX).into();
			let v: ScalingType = value.into();
			let v = Self::convert_units(v * p, unit, AngularUnit::from(STORAGE_UNIT)) * s;
			Self { value: v.into(), scalar: PhantomData }
		}
	}
}

My questions are this:

  1. Why are enum’s no allowed as constants value for generics? (I.e. is there a language design reason for this, or just that it has not been developed yet?)
  2. Is there a better way to pass the STORAGE_PREFIX and STORAGE_UNIT that presents a cleaner interface to the users?

You could use structs and implement a trait for them:

trait StoragePrefixT {}

struct Unit;
struct Micro;
impl StoragePrefixT for Unit {}
impl StoragePrefixT for Micro {}
3 Likes

If you compile on nightly, rustc will tell you that it is allowed when the adt_const_params feature gate is enabled. You also need to annotate your enum with #[derive(ConstParamTy, PartialEq, Eq)].

3 Likes

A classic alternative is to instead pass a "strategy" type which implements a trait with various associated types and constants, rather than passing them as separate const generic parameters.

I don't know whether that's a better or worse fit for your situation, but might be worth thinking about.

EDIT: Fixed link. Thanks, Ctrl-K :confused:

2 Likes

Link is broken:

Bad title
The requested page title contains unsupported characters: "%2B".

That is probably due to (a bug in?) the UI. If you click on the link symbol and paste the URL in the "Link or topic" toolbar, it treats "%" as a symbol to be escaped instead of an escape symbol itself causing the link "https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design" to be "https://en.wikipedia.org/wiki/Modern_C%252B%252B_Design".

@scottmcm, you're better off using the keyboard shortcut [text](url) instead or at least until/if the "insert link" functionality works better.

1 Like

Your suggestion doesn't seem to work on the mobile website with Brave on Android, which is what I'm currently using. But thanks anyway.

Hm, I don't know what to say. I just tried on a Samsung Galaxy S24 running Android 15 using Brave, and the bug happens as I described; meanwhile using the bracket/parentheses form "directly" works.

Additional clarification

RFC 3986 doesn't require "+" to be percent-encoded when it exists in the path of a URI; thus one can use https://en.wikipedia.org/wiki/Modern_C++_Design for most (all?) browsers as is. When that URI is pasted into the "Link or topic" bar and one clicks the "Insert" button, the URI is saved exactly as is; however if the URI is percent-encoded such as https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design, then it will be treated as a non-encoded URI and will be subsequently percent-encoded when saved as https://en.wikipedia.org/wiki/Modern_C%252B%252B_Design after clicking "Insert". However the link will be displayed correctly when you hover over it, but the value that is displayed is not the actual saved URI that will be used when clicked.

Why use an enum for this? It's going to make your life a lot harder than it has to be.

You might want to give my soon-to-be-released units of measure library a look, either for inspiration or else as a dependency :wink:

@ambiso, @scottmcm, thanks for those suggestions. It feels like those are cleaner interfaces than what I am doing currently; but looking at @bjorn3 comments and the issue tracker in git, it seems like we can expect const enums as a first class feature down the road. It feels like I’ll be adding unnecessary complexity that may be harder to refactor in future.

To verify that the interface should work as design (once the feature is in release), I’ll try the nightly that you suggested @bjorn3. Assuming it works, will revert to using i8s and rely on the From<> implementation of the enum to panic!() (if an invalid value is specified). Not super elegant as far as interface design goes, but I think it will be easier to refactor to directly accept the enum consts in future.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.