2
votes

I'd like to define several similar types struct A(Option), struct B(Option) etc., where a floating point value is constrained within a range. The structs may differ further a little bit in their behaviour, but the constructor of each type ist the same: it returns Some(value) if the value is already constrained, otherwise None. To avoid having to implement each constructor individually for each type; I'd like to implement it in the trait. Is this really not possible?

Using the following below, I am told that Self is not a function and that I

can't use Self as a constructor, you must use the implemented struct

.

#[derive(Debug)]
struct A(Option<f64>);

trait Constrained {
    const MIN: f64;
    const MAX: f64;

    fn is_constrained(value: f64) -> bool {
        value >= Self::MIN && value <= Self::MAX
    }

    fn new(value: f64) -> Self where Self: std::marker::Sized {
        if Self::is_constrained(value) {
            Self(Some(value))
        } else {
            Self(None)
        }
    }
}

impl Constrained for A {
    const MIN: f64 = -360.0;
    const MAX: f64 = 360.0;
/*
    fn new(value: f64) -> A {
        if Self::is_constrained(value) {
            Self(Some(value))
        } else {
            Self(None)
        }
    }
*/
}


fn main() {
    let some_val: A = A::new(1000.0);
    println!("{:?}", some_val);
}

Commenting out the implementation of the new function in the trait (starting before where), and uncommenting the impl for each indivdiual struct works of course, but this leads to unncessary code doubling for each type. Is there anything possible or planned to generalize this?

1
Consider writing Constrained as a struct generic over a (possibly zero-sized) type that implements a Constraint trait. The struct governs your data layout and the trait governs behavior. Here's one way to do it, but I can think of several variations.trentcl

1 Answers

1
votes

From your code, it seems that you have several requirements:

  • The implementing struct must provide storage for a field Option<f64>
  • The implementation must provide two constants, MAX and MIN.

What you are really looking for is const generics on the struct. Unfortunately, it is still a highly unstable and incomplete nightly feature as of August 2019. Alternatively, you might resort to macros.

The derive_more crate allows you to derive From<Option<f64>> for each implementating struct tuple that contains exactly one field. That is, you might want this:

#[derive(From)]
pub struct A(Option<f64>);

// just a blanket impl with two consts

Then your trait can require implementors to also implement From<Option<f64>>, which is the required constructor here:

pub trait Constrained : From<Option<f64>> {
    const MAX: f64;
    const MIN: f64;

    // Your is_constrained method
    // Use Self::from(None) instead of Self(None)
}

This improves your attempt in that From can be automatically derived from an existing macro (it is also an idiomatic constructor in Rust style).

The whole point is that Rust traits must not impose any requirements on the precise structure of the implementors. It is also valid for some implementor to have another unused (or irrelevant to Constrained) field; then your trait is not flexible enough.


Side note: it also appears that your trait might be inconvenient to pass around in the future, since trait const items aren't very flexible. Perhaps you will want to use macros-by-example to generate the whole definition+impl in the future.