Which is better between direct const value parameter or const inside trait to generic parameter?

I have two versions of the code

In the first version, I pass the numeric value directly to a const value in the struct, then use the Into trait

use std::marker::PhantomData;

trait Validator<T> {
    fn validate(val: &T) -> Result<(), &'static str>;
}

struct Validated<T, V: Validator<T>>(T, PhantomData<V>);

impl<T, V: Validator<T>> Validated<T, V> {
    fn new(val: T) -> Result<Self, &'static str> {
        V::validate(&val)?;
        Ok(Self(val, PhantomData))
    }
    
    fn get(&self) -> &T {
        &self.0
    }
    
    fn take(self) -> T {
        self.0
    }
}

struct Min<const N: i128>;
impl<T, const N: i128> Validator<T> for Min<N> 
where T: PartialOrd + Copy + Into<i128> {
    fn validate(val: &T) -> Result<(), &'static str> {
        if (*val).into() >= N {
            return Ok(());
        }
        Err("Value too small")
    }
}

struct Tes {
    val: Validated<i32, Min<10>>
}

fn main() -> Result<(), &'static str> {

    let tes = Tes {
        val: Validated::new(20)?
    };
    
    println!("{}", tes.val.take());
    
    Ok(())
}

In the second version, I define a trait containing a const variable and pass that trait into the struct's generic parameter

use std::marker::PhantomData;

trait Validator<T> {
    fn validate(val: &T) -> Result<(), &'static str>;
}

struct Validated<T, V: Validator<T>>(T, PhantomData<V>);

impl<T, V: Validator<T>> Validated<T, V> {
    fn new(val: T) -> Result<Self, &'static str> {
        V::validate(&val)?;
        Ok(Self(val, PhantomData))
    }
    
    fn get(&self) -> &T {
        &self.0
    }
    
    fn take(self) -> T {
        self.0
    }
}

trait Num<T> {
    const val: T;
}

struct Min<N>(PhantomData<N>);
impl<T, N> Validator<T> for Min<N> 
where 
    T: PartialOrd + Copy,
    N: Num<T>
{
    fn validate(val: &T) -> Result<(), &'static str> {
        if *val >= N::val {
            return Ok(());
        }
        Err("Value too small")
    }
}

struct Tes {
    val: Validated<i32, Min<Numm>>
}

struct Numm;
impl Num<i32> for Numm {
    const val: i32 = 10;
}

fn main() -> Result<(), &'static str> {

    let tes = Tes {
        val: Validated::new(20)?
    };
    
    println!("{}", tes.val.take());
   
    Ok(())
}

My goal is to centralize the minimum value, so callers don't have to specify it repeatedly to reducing the risk of inconsistency. I also want the value to be protected, only overridable via ::new() to ensure that everyone creating new value uses a consistent minimum value

Which approach is better?

Is it creating a generic that will accept a generic validation function? Can you elaborate more? I’m confused about the intent because the syntax in the code is not valid. My guess is that it aims to pass a closure to the associated function ::new(), for example : Validated::new(20, above_min::<10>)

But this is exactly what I want to avoid, the caller having to specify the value 10 again. If it's called in multiple places, one caller might use Validated::new(20, above_min::<5>) and it would pass. I want the minimum value setting to be centralized in one place, others should just call it. The caller shouldn't know what the minimum value is, the struct is the only one that know because the minimum value is centralized within the struct. All instances of that struct are then guaranteed to use the same minimum value

I think both of my code above are generic, if I want to create a new one then impl the Validator trait, cmiiw. But creating new validation can seem scary because the many rules in where, I have been thinking about adding macros for syntactic sugar, but my code haven't reached that point yet. I'm still thinking about how to chain the validations, so I'm still confused about which approach to choose. How does your version handle validation chaining?

I already know which one to choose. I’m going with the trait based solution. This is because I want the error messages can be changed rather than hardcoded, and I can't use &'static str in a const generic, it only supports simple types like i32, i64, etc. So, I’m using traits

For the chaining method, I’m creating a new generic struct called And that accepts 2 Validator trait instances, resulting in a structure like And<V1, And<V2, And<V3, V4>>>. Then, I use a macro for syntactic sugar so it looks like validation!(V1, V2, V3)

Is there a better way? My concern with heavy macros is that the compile time might become slow after the macro is called many times