Я придумал что-то, что я использую в своих проектах (пока ржавчина не поддерживает обобщенные константы):
use std::fmt;
pub trait TStaticValue<
V: Copy/*prevent interior mutation (suggested by clippy)*/
> : Sync + 'static + Clone + fmt::Debug { // these bounds turned out to be convenient for me
const VALUE : V;
}
macro_rules! define_static_value {(pub $struct: ident, $type: ty, $value: expr) => {
#[derive(Clone, Debug)]
pub struct $struct {}
impl TStaticValue<$type> for $struct {
const VALUE : $type = $value;
}
}}
Тогда define_static_value
позволит вам определить следующее:
define_static_value!(pub ForwardDelta, i32, 1);
define_static_value!(pub BackwardDelta, i32, -1);
pub struct Indexer<Dir: TStaticValue<i32>> {
pd: std::marker::PhantomData<Dir>
}
impl<Dir: TStaticValue<i32>> Indexer<Dir> {
const DELTA:i32 = Dir::VALUE;
}
Я знаю, что вы хотели избежать макросов, но это решение оказалось для меня относительно легким в обслуживании, потому что все "значения stati c" (т.е. параметры const generi c) эмулируются одинаково, и это позволяет Вы можете определить и использовать их как ho c и с разными типами, все через одну и ту же архитектуру: всякий раз, когда вам действительно нужен параметр const generi c Param
типа CG
, вместо этого вы указываете Param: TStaticValue<CG>
.
Возможно, вы захотите настроить define_static_value
, чтобы учесть значения, отличные от pub
.
Замечание относительно PhandomData
: я до сих пор не нашел способа, который позволил бы мне избегайте их, но вы можете убедиться, что каждая структура имеет не более одного PhantomData
члена, упаковав все остальные неиспользуемые параметры в это соответствующее поле (которое тогда будет иметь тип PhantomData<(FirstUnused, SecondUnused, ThirdUnused)>
.