惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
A
About on SuperTechFans
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
博客园 - Franky
D
Docker
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Last Week in AI
Last Week in AI
U
Unit 42
F
Fortinet All Blogs
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
P
Proofpoint News Feed
月光博客
月光博客
G
Google Developers Blog

Whexy Blog

关于身体 替尔泊肽 We lost the AIxCC. So, what now? Arm VMM with Apple's Hypervisor Framework Driving WaveShare E‐Paper Display with a Raspberry Pi Pico in MicroPython Use cgroup v2 inside docker containers Annual Hit Piece: Fuzzing Top Conference Paper Debunking Report Solving SSH Key Login Issues on Synology NAS Can SSD Cache Improve Synology NAS Write Speeds? Virtualization is all you need Running Windows Games on Mac Without Virtual Machines Tears of the Kingdom: End of an Era Anonymous CDN Traffic Relay Self-host Relay Service with CDN Home Networking Solution Building Your Own Blog System Connecting Smart Devices to SUSTech Campus Network Function Color Theory Stop Forkin' Around: Faster Creating of Large Processes on Linux PMU Interrupts: How to handle them Asynchronous Mutex Using QEMU to run Linux images on M1 Macbook Alligator In Vest - My first research work Experience Using Several Plugins in Complex LaTeX Projects Variance in Rust SUSTeam: Ultimate Gaming Platform Inline Assembly Language in C React Learning Notes Building a School Bus Schedule App for Apple Watch 12307 Train Ticket Purchase Platform
Understanding Rust Generic Traits
Whexy · 2021-02-01 · via Whexy Blog

Whexy /

February 01, 2021

In Rust, traits can also be generic. Generic traits are introduced for two reasons: first, to make traits not limited by specific types, and second, to provide broader constraint capabilities.

In Rust, "casting" is actually a kind of trait. In a world without generic traits, how should type conversion work?

We could first write a trait called CastFromI32. All types that implement the CastFromI32 trait can be cast from the i32 type.

struct MyType {}

pub trait CastFromI32 {
  fn from(_: i32) -> Self;
}

impl CastFromI32 for MyType {
  fn from(origin: i32) -> Self {
    // -- cast code --
  }
}

This is far from enough. Now, I also want to be able to cast from other types like i64/u32/u64/f8/f32/... This means we need to write a series of traits like CastFromI64/CastFromU32/..., and then expect developers to implement them one by one.

struct MyType {}

pub trait CastFromI32 {
  fn from(_: i32) -> Self;
}

pub trait CastFromI64 {
  fn from(_: i64) -> Self;
}

pub trait CastFromU32 {
  fn from(_: u32) -> Self;
}

// pub trait CastFrom ...
// damn, so much!

This is undoubtedly tedious. But with generic traits, we can do this:

struct MyType {}

pub trait CastFrom<T> {
  fn from(_: T) -> Self;
}

Generic traits make "traits not limited by specific types." This allows us to write more concise code. Developers can also use macros to reduce workload.

Traits are similar to interfaces in other languages, providing constraints. In object-oriented programming, we often write interfaces like Flyable and Eatable to distinguish ducks and pizzas from other classes. Traits in Rust work similarly.

"Operator overloading" is also implemented through traits. Let's first look at what the addition trait looks like.

pub trait Add<Rhs = Self> {
    /// The resulting type after applying the `+` operator.
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

Here, Rhs is a generic that specifies the type of the addend, defaulting to the same type as the adder (Self). Output is an associated type that specifies the type of the addition result. Associated types are a special form of generics.

By implementing the Add trait, I would write addition for a complex number class like this:

struct Complex {
    real: f64,
    imag: f64,
}

impl Add for Complex {
    type Output = Complex;

    fn add(self, rhs: Self) -> Self::Output {
        Complex {
            real: self.real + rhs.real,
            imag: self.imag + rhs.imag,
        }
    }
}

But as an excellent library author, you also want Complex to support more data types for the underlying real and imaginary parts, such as i64/u32/u64/f8/f32/... This means we need to make Complex generic, roughly like this:

struct Complex<T> {
    real: T,
    imag: T,
}

impl<T> Add for Complex<T> {
    type Output = Complex<T>;

    fn add(self, rhs: Self) -> Self::Output {
        Complex {
            real: self.real + rhs.real, // compile error ❌️ here!
            imag: self.imag + rhs.imag,
        }
    }
}

Compilation... error! This is because the generic T we use could be any type, such as a duck or pizza, which don't have addition operations. The compiler is smart enough to detect this. So, T needs to be constrained. The constraint condition is "T has addition, and both the addend type and result type are also T", which translates to Rust as T: Add<T, Output=T>.

In Add<T, Output=T>, the first T refers to Rhs, the type of the addend. In the Add trait, we already mentioned the default value is Self, so we can omit it here. The second T is the associated type Output, the type of the result, which needs to be specified as T.

So the correct way to write it is:

struct Complex<T> {
    real: T,
    imag: T,
}

impl<T> Add for Complex<T>
    where T: Add<Output=T>   // or, `where T: Add<T, Output=T>`
{
    type Output = Complex<T>;

    fn add(self, rhs: Self) -> Self::Output {
        Complex {
            real: self.real + rhs.real,
            imag: self.imag + rhs.imag,
        }
    }
}

Reviewing our changes: we added constraints to the previously unrestricted type T. This constraint condition "T has addition, and both the addend type and result type are also T" is quite abstract. We make the constraint valid by adjusting the specific types in the generic Add trait. Now, you should understand why I say "generic traits have broader constraint capabilities."

When writing this article, it was my second day of working with Rust. The content certainly has many shortcomings, so please feel free to correct me.

If you find this helpful, you can continue following my blog. There's an RSS subscription link below.

Please do not repost without permission.

© LICENSED UNDER CC BY-NC-SA 4.0