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

推荐订阅源

D
Docker
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
博客园 - 司徒正美
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
量子位
罗磊的独立博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
小众软件
小众软件
C
Cybersecurity and Infrastructure Security Agency CISA
Cyberwarzone
Cyberwarzone
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园_首页
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
IT之家
IT之家
博客园 - 聂微东
Spread Privacy
Spread Privacy
V2EX - 技术
V2EX - 技术
S
Security Affairs
宝玉的分享
宝玉的分享
V
V2EX
C
Cisco Blogs
博客园 - Franky
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
S
Securelist
J
Java Code Geeks
Webroot Blog
Webroot Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Proofpoint News Feed
Last Week in AI
Last Week in AI
L
LINUX DO - 热门话题
NISL@THU
NISL@THU
WordPress大学
WordPress大学
W
WeLiveSecurity
T
Threatpost
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
阮一峰的网络日志
阮一峰的网络日志

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 Sakai and Local Folder Synchronization Building a Super Simple OpenJudge in Two Nights Setting Up Remote Backup for macOS Shell Script for Automatically Logging into SUSTech Campus Network Building a Movie Streaming System in the Dorm
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