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