Class Extensions in Rust

What is the rust Class Extensions method, that is equivalent to the below:

Kotlin:

fun Int.square(): Int {
  return this * this
}

println(5.square()) //prints 25

Swift

extension Int {
  func square() -> Int {
    return self * self
  }
}

print(5.square()) //prints 25

C#

    public static class IntExtensions
     {
        public static int square(this int i)
        {
            return i * i;
        }
    }

Console.WriteLine(5.square());  // prints 25
3 Likes

Rust uses a design pattern called extension traits. They work in a similar manner like above class extensions, but are required to be explicitly used. This makes it easier to trace back where a particular method is coming from.

mod foo {
    pub trait SquareExt {
        fn square(self) -> Self;
    }

    impl SquareExt for i32 {
        fn square(self) -> Self { self * self }
    }
}

fn main() {
    use foo::SquareExt;
    println!("{}", 5.square());
}
2 Likes

When I tried it at the playground I got the below error:

  Compiling playground v0.0.1 (/playground)
error[E0603]: trait `SquareExt` is private
  --> src/main.rs:12:9
   |
12 |     use foo::SquareExt;
   |         ^^^^^^^^^^^^^^

warning: unused import: `foo::SquareExt`
  --> src/main.rs:12:9
   |
12 |     use foo::SquareExt;
   |         ^^^^^^^^^^^^^^
   |
   = note: #[warn(unused_imports)] on by default

error[E0689]: can't call method `square` on ambiguous numeric type `{integer}`
  --> src/main.rs:13:22
   |
13 |     println!("{}", 5.square());
   |                      ^^^^^^
help: you must specify a concrete type for this numeric value, like `i32`
   |
13 |     println!("{}", 5_i32.square());
   |                    ^^^^^

error: aborting due to 2 previous errors

Some errors occurred: E0603, E0689.
For more information about an error, try `rustc --explain E0603`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

While the below worked fine with me:

trait SquareExt {
    fn square(self) -> Self;
}
    
impl SquareExt for i32 {
    fn square(self) -> Self { self * self }
}

fn main() {
    println!("{}", 5.square());    // prints 25
}

Which had been modified to handle another parameter as below:

trait SquareExt {
    fn square(self, i32) -> Self;
    // fn square(self, _:  i32) -> Self;  // in Rust 2018, name is a must even if it is just _
}
    
impl SquareExt for i32 {
    fn square(self, i: i32) -> Self { self * i }
}

fn main() {
    println!("{}", 5.square(2));    // prints 10
}

Try to change the trait SquareExt to public:

pub trait SquareExt ...

playground

1 Like

Thanks.
How can I implement it for generat type T, I tried:

    use std::ops::Mul;
    impl<T: Mul> SquareExt 
    for T 
    where T: Mul
    {
        fn square(self) -> Self { self * self }
    }

but got the below error:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:11:35
   |
11 |         fn square(self) -> Self { self * self }
   |                            ----   ^^^^^^^^^^^ expected type parameter, found associated type
   |                            |
   |                            expected `T` because of return type
   |
   = note: expected type `T`
              found type `<T as std::ops::Mul>::Output`

Thankig to this, I found the solution required using <Output = T> because of the return type, and require using copy if self is used more than one, as it will be moved after the first usage, so the final solution I've is:

mod foo {
    pub trait SquareExt {
        fn square(self) -> Self;
    }

    use std::ops::Mul;
    impl<T> SquareExt 
    for T 
    where  T: Mul<Output = T> + Copy
    {
        fn square(self) -> T { self * self }
    }
}

fn main() {
    use foo::SquareExt;
    // or with Rust 2018
    // use self::foo::SquareExt;
    println!("{}", 5.1.square());     // prints 26.009999999999998
}
5 Likes

Note that you don't have to require T: Mul<Output = T>. You can just allow square() to return the same type Mul returns:

mod foo {
    pub trait SquareExt {
        type Output;
        fn square(self) -> Self::Output;
    }

    use std::ops::Mul;
    impl<T> SquareExt for T
    where
        T: Mul + Copy,
    {
        type Output = <T as Mul>::Output;
        fn square(self) -> Self::Output {
            self * self
        }
    }
}
5 Likes

Thanks! It was really helpful. Cheers!