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

推荐订阅源

V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Secure Thoughts
Hacker News - Newest:
Hacker News - Newest: "LLM"
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
TaoSecurity Blog
TaoSecurity Blog
博客园 - Franky
有赞技术团队
有赞技术团队
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
NISL@THU
NISL@THU
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Commits to openclaw:main
Recent Commits to openclaw:main
K
Kaspersky official blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
W
WeLiveSecurity
SecWiki News
SecWiki News
Google DeepMind News
Google DeepMind News
O
OpenAI News
V
V2EX
AI
AI
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research
大猫的无限游戏
大猫的无限游戏
美团技术团队
C
Cyber Attacks, Cyber Crime and Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 三生石上(FineUI控件)
G
GRAHAM CLULEY
月光博客
月光博客
T
Threatpost
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Vulnerabilities – Threatpost
Last Week in AI
Last Week in AI
爱范儿
爱范儿
C
CXSECURITY Database RSS Feed - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cloudbric
Cloudbric
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
N
News | PayPal Newsroom
Know Your Adversary
Know Your Adversary

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