Home > front end >  Is there a way to `overload` the same method but with different parameters?
Is there a way to `overload` the same method but with different parameters?

Time:05-17

It was a beautiful day until I saw python's Tkinter ability to get/set data on a value, like follows:

k = some_object(inner=7)
print(k.inner()) # Gets the value, prints 7
k.inner(4) # Sets the value
print(k.inner()) # 4

I was impressed - that is very cool! No need to write all these pesky set_data or get_data, just a single method that decides what to do all by itself!

But then I tried the same thing in Rust. Firstly, that inner can't be a function, since Rust's functions do not support overloading. This problem is often solved by manually implementing FnOnce, FnMut and Fn on some unit type and then declaring a constant of that unit(see overloadf crate). Although it requires some nightly features, I am currently using nightly Rust so it's not a problem.

But what is a problem is that method takes self as an argument(that's why it's method and not a function), and you cannot 'call' a constant using dot notation:

#![feature(fn_traits, unboxed_closures)]

/// Some `sort` of a function
pub struct Functor;

impl FnOnce for Functor { ... }

impl FnMut for Functor { ... }

impl Fn for Functor { ... }

#[allow(#[allow(non_upper_case_globals)])]
pub const i_am_a_normal_function_trust_me: Functor = Functor;

// ...

i_am_a_normal_function_trust_me() // Yay! Could be called!

pub struct Wrapper(());

impl Wrapper {
    /// Let's just say that `Functor` takes a `&Wrapper`
    #[allow(non_upper_case_globals)]
    pub const method: Functor = Functor;
}

let v = Wrapper(());

// Oopsy, does not work
v.method()

// Only through that ugly notation(which is not what I want)
Wrapper::method(&v)

This does not compile, so I tried an another approach - through ordinary traits:

pub trait GetInner <T> {
    fn inner(&self) -> &T;
}

pub trait SetInner <T> {
    fn inner(&mut self, inner: T);
}

pub struct Wrapper <T> (T);

impl <T> GetInner <T> for Wrapper <T> {
    fn inner(&self) -> &T {
        &self.0
    }
}

impl <T> SetInner <T> for Wrapper <T> {
    fn inner(&mut self, inner: T) {
        self.0 = inner
    }
}

fn main() {
    let mut v = Wrapper(0);
    dbg!(v.inner());
    v.inner(4); // error[E0061]: this function takes 0 arguments but 1 argument was supplied
    dbg!(v.inner());
    v.inner(v.inner()   2); // error[E0061]: this function takes 0 arguments but 1 argument was supplied
    dbg!(v.inner());
}

So... Compiler couldn't distinguish between methods of separate traits even though their signature is completely different!

Okay, nice try, nice try... But Polska the idea is not yet lost!

This time I noticed that compiler complains only about SetInner and tried swapping impls:


pub trait GetInner <T> {
    fn inner(&mut self) -> &T;
}

pub trait SetInner <T> {
    fn inner(&mut self, inner: T);
}

pub struct Wrapper <T> (T);

/// Swappy swap
impl <T> SetInner <T> for Wrapper <T> {
    fn inner(&mut self, inner: T) {
        self.0 = inner
    }
}

/// Swappy swap
impl <T> GetInner <T> for Wrapper <T> {
    fn inner(&mut self) -> &T {
        &self.0
    }
}

fn main() {
    let mut v = Wrapper(0);
    dbg!(v.inner()); // error[E0034]: multiple applicable items in scope
    v.inner(4); // error[E0034]: multiple applicable items in scope
    dbg!(v.inner()); // error[E0034]: multiple applicable items in scope
    v.inner(v.inner()   2); // error[E0034]: multiple applicable items in scope x 2
    dbg!(v.inner()); // error[E0034]: multiple applicable items in scope
}

Weird, errors are completely different now. But this doesn't help me...

I ask for a bright guidance from above: is there any way to implement my wish into reality, or current Rust(even nightly) is not yet prepared for such an 'advanced technology'?

CodePudding user response:

Don't. Don't implement overloading in Rust. Don't even try. This is not a good Rust API design. Rust does not use overloading. Rust should not use overloading. Use traits.


Sure, but still, can we?

The trait approach does not work with multiple traits, but it will work with a single generic trait (though the call is... not convenient):

trait Overload<Args> {
    type Output;
    fn foo(&mut self, args: Args) -> Self::Output;
}

struct Data(i32);

impl Overload<()> for Data {
    type Output = i32;
    fn foo(&mut self, (): ()) -> i32 { self.0 }
}

impl Overload<(i32,)> for Data {
    type Output = ();
    fn foo(&mut self, (v,): (i32,)) { self.0 = v; }
}

fn main() {
    let mut v = Data(0);
    v.foo((123,));
    dbg!(v.foo(()));
}

Playground.

I haven't (yet?) found a way to make the Fn* approach works, sadly.

CodePudding user response:

mimicking the Tkinter ability in a rustier way:

struct SomeObject {
    inner: i32,
}

impl SomeObject {
    fn new() -> SomeObject {
        SomeObject { inner: 0, }
    }

    fn inner(&mut self, opt: Option<i32>) -> i32 {
        match opt {
            Some(n) => { self.inner = n; n },
            None    => self.inner,
        }
    }
}

fn main() {
    let mut some_object = SomeObject::new();
    some_object.inner(Some(123));
    println!("{}", some_object.inner(None));  // 123
}
  • Related