Param Verification Abstraction

Hello,

I am trying to implement a 2-side interface (one interface for the user, and one interface for the implementor) with code in-between which does parameter validation (and more...) in a middle-layer which is common for all implementations. I have written a small C++ example to demonstrate what I am trying to do:

class A {
public:
    // foo() is the API method which ensures for all implementations that the given parameters have valid values.
    int foo(int x, int y) {
        if (x >= 10) {
            throw std::runtime_error("x must be less than 10.");
        }
        if (y >= 10) {
            throw std::runtime_error("y must be less than 10.");
        }
        return foo_impl(x, y);
    }

protected:
    // foo_impl() must be implemented by the inherited classes.
    // They do not need to check if the given parameters are valid.
    virtual int foo_impl(int x, int y) = 0;
};

class B final : public A {
public:
    B(int z) : m_z(z) {

    }

protected:
    int foo_impl(int x, int y) override {
        return x + y + m_z;
    }

private:
    int m_z;
};

class C final : public A {
public:
    C(int z) : m_z(z) {

    }

protected:
    int foo_impl(int x, int y) override {
        return x - y + m_z;
    }

private:
    int m_z;
};

int main() {
    B b(5);
    C c(5);
    std::cout << b.foo(1, 2) << std::endl;
    std::cout << c.foo(1, 2) << std::endl;
    return 0;
}

Trying to do the same in Rust results in this code:

trait A {
    fn foo(&self, x: i32, y: i32) -> Result<i32, &'static str> {
        if x >= 10 {
            return Err("x must be less than 10.")
        }
        if y >= 10 {
            return Err("y must be less than 10.")
        }
        Ok(self.foo_impl(x, y))
    }

    fn foo_impl(&self, x: i32, y: i32) -> i32;
}

struct B {
    z: i32,
}

impl B {
    pub fn new(z: i32) -> B {
        B {
            z,
        }
    }
}

impl A for B {
    fn foo_impl(&self, x: i32, y: i32) -> i32 {
        return x + y + self.z;
    }
}

struct C {
    z: i32,
}

impl C {
    pub fn new(z: i32) -> C {
        C {
            z,
        }
    }
}

impl A for C {
    fn foo_impl(&self, x: i32, y: i32) -> i32 {
        return x - y + self.z;
    }
}

fn main() {
    let b = B::new(5);
    let c = C::new(5);
    println!("{}", b.foo(1, 2).unwrap());
    println!("{}", c.foo(1, 2).unwrap());
}

The problem here which is bugging me is that the associated function foo_impl() is public to the user, and it should be private. A simple solution I thought about was to declare this method as unsafe, but then the implementation will be unsafe also and it would be possible that in a in general "safe" function the implementor could write unsafe code by accident.

Then I thought about using templates. Again, a C++ example:

template<class Impl> class A {
public:
    A(int z) : m_impl(z) {

    }

    int foo(int x, int y) {
        if (x >= 10) {
            throw std::runtime_error("x must be less than 10.");
        }
        if (y >= 10) {
            throw std::runtime_error("y must be less than 10.");
        }
        return m_impl.foo_impl(x, y);
    }

private:
    Impl m_impl;
};

class BImpl final {
    friend class A<BImpl>;

public:
    BImpl(int z) : m_z(z) {

    }

private:
    int m_z;

    int foo_impl(int x, int y) {
        return x + y + m_z;
    }
};

class CImpl final {
    friend class A<CImpl>;

public:
    CImpl(int z) : m_z(z) {

    }

private:
    int m_z;

    int foo_impl(int x, int y) {
        return x - y + m_z;
    }
};

using B = A<BImpl>;
using C = A<CImpl>;

int main() {
    B b(5);
    C c(5);
    std::cout << b.foo(1, 2) << std::endl;
    std::cout << c.foo(1, 2) << std::endl;
    return 0;
}

Trying to do the same in Rust results in:

mod a {
    pub struct A<Ai: AImpl> {
        a_impl: Ai,
    }

    impl<Ai: AImpl> A<Ai> {
        // Using move
        pub fn new(a_impl: Ai) -> A<Ai> {
            A {
                a_impl,
            }
        }

        // But would like to so something like that:
        // pub fn new(z: i32) -> A<Ai> {
        //     A {
        //         a_impl: Ai::new(z),
        //     }
        // }

        pub fn foo(&self, x: i32, y: i32) -> Result<i32, &'static str> {
            if x >= 10 {
                return Err("x must be less than 10.")
            }
            if y >= 10 {
                return Err("y must be less than 10.")
            }
            Ok(self.a_impl.foo_impl(x, y))
        }
    }

    pub trait AImpl {
        fn foo_impl(&self, x: i32, y: i32) -> i32;
    }
}

mod b {

    pub struct B {
        z: i32,
    }

    impl B {
        pub fn new(z: i32) -> B {
            B {
                z,
            }
        }
    }

    impl super::a::AImpl for B {
        fn foo_impl(&self, x: i32, y: i32) -> i32  {
            x + y + self.z
        }
    }

}

mod c {

    pub struct C {
        z: i32,
    }

    impl C {
        pub fn new(z: i32) -> C {
            C {
                z,
            }
        }
    }

    impl super::a::AImpl for C {
        fn foo_impl(&self, x: i32, y: i32) -> i32  {
            x - y + self.z
        }
    }

}

fn main() {
    let b = a::A::new(b::B::new(5));
    let c = a::A::new(c::C::new(5));
    println!("{}", b.foo(1, 2).unwrap());
    println!("{}", c.foo(1, 2).unwrap());
}

I have added some modules in the above example to check visibility problems. What is bugging me here is that I must use a move in the constructor (B::new() and C::new()) instead of the original parameter value and that the foo_impl() method is public again.

I would be grateful if you have an Idea how I could reimplement this concept in Rust in a simple way :slight_smile:

You can return an A<B> from the B::new method to simplify the construction for the user.

I have not stated this above, but I would like to have the possibility to validate the parameters (or do some common pre-calculations) for the construction (without any explicit code within the implementation B or C) also.

In C++ I would do:

class A {
public:
    A(int z) {
        if (z >= 10) {
            throw std::runtime_error("x must be less than 10.");
        }
    }

// ...

class B final : public A {
public:
    B(int z) : A(z), m_z(z) {

    }

// ...

or in the template-example it would be even nicer because of no explicit constructor call of the base class within the derived class:

template<class Impl> class A {
public:
    A(int z) : m_impl(validate_params(z)) {
        
    }

    int foo(int x, int y) {
        if (x >= 10) {
            throw std::runtime_error("x must be less than 10.");
        }
        if (y >= 10) {
            throw std::runtime_error("y must be less than 10.");
        }
        return m_impl.foo_impl(x, y);
    }

private:
    Impl m_impl;

    static Impl validate_params(int z) {
        if (z >= 10) {
            throw std::runtime_error("x must be less than 10.");
        }
        return Impl(z);
    }
};

In Rust I would try:

impl B {
        pub fn new(z: i32) -> Result<super::a::A<B>, &'static str> {
            let res = super::a::A::<B>::validate_params(z);
            if let Err(err) = super::a::A::<B>::validate_params(z) {
                return Err(err)
            }
            Ok(super::a::A::new(B {
                z,
            }))
        }
    }

// ...

let b = b::B::new(5).unwrap();

But the problem I see here is that there must be explicit code withing the implementation B, and I want that the implementor can not do any mistakes, that he can expect that the constructor method B::new() or C::new() and foo_impl() are only called when the parameters are valid and never ever if they are invalid, without any explicit validation code within the implementations.

I think in rust we would probably use newtypes with constructors (returning Option<Self> or Result<Self, Error>) enforcing the invariants.

Something like:

mod foo {
    struct A(i32);
    struct B(i32);

    impl A {
        pub fn new(v: i32) -> Option<A> {}
    }
    impl B {
        pub fn new(v: i32) -> Option<B> {}
    }
}

struct Foo(foo::A, foo::B);

fn new_foo(a: i32, b: i32) -> Option<Foo> {
   Some(Foo(foo::A::new(a)?, foo::B::new(b)?))
}

There is the hidden attribute.

@erelde I do not see how this solution solves my problem.

@stonerfish Thank you for the link. Unfortunately, I do not want the code to be excluded from the documentation only but also from access by the user.

I found another solution to hide the access of foo_impl() from the user by introducing another module above and re-export only B and C without A. Unfortunately, this has another draw-back which I have stated in the comments below:

mod foo {
    mod a {
        pub struct A<Ai: AImpl> {
            a_impl: Ai,
        }

        impl<Ai: AImpl> A<Ai> {
            pub fn new(a_impl: Ai) -> A<Ai> {
                A {
                    a_impl,
                }
            }

            pub fn foo(&self, x: i32, y: i32) -> Result<i32, &'static str> {
                if x >= 10 {
                    return Err("x must be less than 10.")
                }
                if y >= 10 {
                    return Err("y must be less than 10.")
                }
                Ok(self.a_impl.foo_impl(x, y))
            }

            pub fn validate_params(z: i32) -> Result<(), &'static str> {
                if z >= 10 {
                    return Err("z must be less than 10.")
                }
                Ok(())
            }
        }

        pub trait AImpl {
            fn foo_impl(&self, x: i32, y: i32) -> i32;
        }
    }

    mod b {

        pub struct B {
            z: i32,
        }

        impl B {
            pub fn new(z: i32) -> Result<super::a::A<B>, &'static str> {
                let res = super::a::A::<B>::validate_params(z);
                if let Err(err) = super::a::A::<B>::validate_params(z) {
                    return Err(err)
                }
                Ok(super::a::A::new(B {
                    z,
                }))
            }
        }

        impl super::a::AImpl for B {
            fn foo_impl(&self, x: i32, y: i32) -> i32  {
                x + y + self.z
            }
        }

    }

    mod c {

        pub struct C {
            z: i32,
        }

        impl C {
            pub fn new(z: i32) -> super::a::A<C> {
                super::a::A::new(C {
                    z,
                })
            }
        }

        impl super::a::AImpl for C {
            fn foo_impl(&self, x: i32, y: i32) -> i32  {
                x - y + self.z
            }
        }
    }

    pub use b::B;
    pub use c::C;
}

// This does not work anymore... New implementations can not be added outside of the module anymore,
// which prevents the user from implementing new implementations without touching the library code.
// pub struct D {
//     z: i32,
// }

// impl D {
//     pub fn new(z: i32) -> foo::a::A<D> {
//         foo::a::A::new(D {
//             z,
//         })
//     }
// }

// impl foo::a::AImpl for D {
//     fn foo_impl(&self, x: i32, y: i32) -> i32  {
//         x - y + self.z
//     }
// }

fn main() {
    let b = foo::B::new(5).unwrap();
    let c = foo::C::new(5);
    println!("{}", b.foo(1, 2).unwrap());
    println!("{}", c.foo(1, 2).unwrap());
}

What I am looking for is a Rust equivalent of my C++ implementation which has the same properties related to safety and flexibility:

  • Parameters for methods and constructions must be validated for all implementations without explicit code in the implementations (e.g. no validate_params() function call in each foo_impl() or new() method within the implementations). It must be ensured that the implementations always get valid parameters or the methods will not be called. The implementor should not be able to forget to validate the parameters.
  • Methods without validation must not be able to be called by the user.
  • New implementations can be added by the user externally and the 2 properties above must still be valid.

I am thinking about another solution:

mod foo {
    pub struct NewParams {
        // z must be private to ensure that the value can not be changed after the validation in the construction.
        z: i32,
    }
    impl NewParams {
        pub fn new(z: i32) -> Result<NewParams, &'static str> {
            if z >= 10 {
                return Err("z must be less than 10.")
            }
            Ok(NewParams {
                z,
            })
        }
        // A public getter to allow access from external defined implementations.
        pub fn z(&self) -> i32 {
            self.z
        }
    }
    pub struct FooParams {
        // x and y must be private to ensure that the value can not be changed after the validation in the construction.
        x: i32,
        y: i32,
    }
    impl FooParams {
        pub fn new(x: i32, y: i32) -> Result<FooParams, &'static str> {
            if x >= 10 {
                return Err("x must be less than 10.")
            }
            if y >= 10 {
                return Err("y must be less than 10.")
            }
            Ok(FooParams {
                x,
                y,
            })
        }
        // A public getter to allow access from external defined implementations.
        pub fn x(&self) -> i32 {
            self.y
        }
        // A public getter to allow access from external defined implementations.
        pub fn y(&self) -> i32 {
            self.y
        }
    }
    pub trait A {
        fn foo(&self, params: &FooParams) -> i32;
    }
    pub struct B {
        z: i32,
    }
    impl B {
        pub fn new(params: &NewParams) -> B {
            B {
                z: params.z,
            }
        }
    }
    impl A for B {
        fn foo(&self, params: &FooParams) -> i32 {
            // It is enusred that the parameters are valid here. No error-handling necessary by the implementation.
            params.x + params.y + self.z
        }
    }
    pub struct C {
        z: i32,
    }
    impl C {
        pub fn new(params: &NewParams) -> C {
            C {
                z: params.z,
            }
        }
    }
    impl A for C {
        // It is enusred that the parameters are valid here. No error-handling necessary by the implementation.
        fn foo(&self, params: &FooParams) -> i32 {
            params.x - params.y + self.z
        }
    }
}

// Also, new implementations can be added externally,
// and the public getters for x, y and z can be used to get access to the parameters.
pub struct D {
    z: i32,
}
impl D {
    pub fn new(params: &foo::NewParams) -> D {
        D {
            z: params.z(), // Using getters...
        }
    }
}
impl foo::A for D {
    // It is enusred that the parameters are valid here. No error-handling necessary by the implementation.
    fn foo(&self, params: &foo::FooParams) -> i32 {
        params.x() * params.y() + self.z // Using getters...
    }
}

fn main() {
    use foo::A;
    let b = foo::B::new(&foo::NewParams::new(5).unwrap());
    let c = foo::C::new(&foo::NewParams::new(5).unwrap());
    let b_foo_params = foo::FooParams::new(1, 2).unwrap();
    let c_foo_params = foo::FooParams::new(3, 4).unwrap();
    // Parameters must be validated once only and can then be reused without re-validation.
    println!("{}", b.foo(&b_foo_params));
    println!("{}", c.foo(&c_foo_params));
    println!("{}", b.foo(&b_foo_params));
    println!("{}", c.foo(&c_foo_params));
    // Using an external implementation...
    let d = D::new(&foo::NewParams::new(5).unwrap());
    println!("{}", d.foo(&b_foo_params));
}

Here I am using parameter structs to abstract the parameter validation and it has the benefits that it looks very simple (no over-engineering) and that the parameters must be validated only once, which means that the method foo() can be recalled without re-validation (in case foo() has any side-effects this could be beneficial). All conditions stated above are met but it has the draw-backs that the user-code becomes a little bit more complex and only parameter validation will be possible (no other code with some side-effects within the "base-struct" (sorry, I know this is C++'ish ^^). But In general the first Rust example of this post is C++ inheritance somehow re-implemented in Rust).

I think I stick with this solution for now, but ideally I am looking for a solution similar in usage like the C++ implementation.

How about something like:

mod a {
    // These could have differnet function names if you like so a struct could
    // implement both or to reduce potential confusion.
    pub trait AImpl {
        fn foo(&self, data: &AData, x: i32, y: i32) -> i32;
    }
    pub trait AUser {
        fn foo(&self, x: i32, y: i32) -> Result<i32, &'static str>;
    }

    // Common data
    pub struct AData { pub z: i32 }

    // The safe user wrapper
    pub struct A<T> {
        data: AData,
        implementor: T,
    }

    impl<T: AImpl> A<T> {
        pub fn new(implementor: T, z: i32) -> Result<A<T>, &'static str> {
            Ok(A {
                data: AData { z },
                implementor
            })
        }
    }

    impl<T: AImpl> AUser for A<T> {
        fn foo(&self, x: i32, y: i32) -> Result<i32, &'static str> {
            if x >= 10 { return Err("x must be less than 10.") }
            if y >= 10 { return Err("y must be less than 10.") }
            Ok(self.implementor.foo(&self.data, x, y))
        }
    }
}

mod b {
    use super::a::{AImpl, AData};

    // Implementation 1
    pub struct B;
    impl AImpl for B {
        fn foo(&self, data: &AData, x: i32, y: i32) -> i32 { x + y + data.z }
    }
}

// Implementation 2
mod c {
    use super::a::{AImpl, AData};

    pub struct C { m: i32 }
    impl C {
        pub fn new(m: i32) -> C { C { m } }
    }
    impl AImpl for C {
        fn foo(&self, data: &AData, x: i32, y: i32) -> i32 { x - y + data.z + self.m }
    }
}


fn main() {
    use a::AUser;

    let b = a::A::new(b::B, 5).unwrap();
    let c = a::A::new(c::C::new(2), 5).unwrap();
    println!("{}", b.foo(1, 2).unwrap());
    println!("{}", c.foo(1, 2).unwrap());
}

Variations:

  • AData could expose members as #[inline] methods so that only A<T> is allowed to construct or modify one.

  • AData could have a constructor and the data struct could be explicitly passed to B and C constructors if that data is needed at construction time for B and C.

  • AUser trait isn't strictly necessary since A<T> would probably be the only implementor.

@duelafn Thank you, this would work. The problem I see here is that the z-value must be passed each time foo() is called. The example I have given is a very simplified version of the problem I am trying to solve. The software I am implementing is doing computation intensive calculations with each z value (it's a cryptographic key to be more precisely...) differently for each implementation and will store pre-calculated values (e.g. round-keys for AES encryption) permanently in the implementors struct to be reused for each call of foo().

This library will implement a set of default ciphers which are currently in use, but users of this library must be able to add new ciphers without touching the library code.

Because it is about cryptographic algorithms, the security requirements are definied that high. In general, neither the user nor the implementor should be able to do any mistakes (at least this is the aspired ideal) and the API (user-side and implementor-side) must be as simple as possible (No-one should be confused by overengineered code to enforce this requirements, too much complexitiy seen by the user/implementor would introduce too much confusion and by that increase the probability that something will be implemented wrong).

See variation 2. Construct AData, pass it to C constructor which pre-computes what it needs from that data (but doesn't need to clone it since it will get a copy on each Impl call). Receiving data happens as a reference just like getting &self so size and complexity of AData doesn't matter.

Yes, I think this will work :slight_smile:

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.