练习题来自:https://practice-zh.course.rs/generics-traits/advanced-traits.html
1
rust
struct Container(i32, i32);
// 使用关联类型实现重新实现以下特征
// trait Contains {
// type A;
// type B;
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
impl Contains<i32, i32> for Container {
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// Grab the first number.
fn first(&self) -> i32 { self.0 }
// Grab the last number.
fn last(&self) -> i32 { self.1 }
}
fn difference<A, B, C: Contains<A, B>>(container: &C) -> i32 {
container.last() - container.first()
}
fn main() {
let number_1 = 3;
let number_2 = 10;
let container = Container(number_1, number_2);
println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());
println!("The difference is: {}", difference(&container));
}
所谓关联类型,个人觉得更像是一种语法糖。它将本来需要用泛型表示的类型写在特征里面。实现特征,不仅需要实现特征中的方法,还要实现特征中的类型。
将A和B作为关联类型写入特征:
rust
trait Contains{
type A;
type B;
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
实现特征时,显式指定A和B的类型:
rust
impl Contains for Container {
type A = i32;
type B = i32;
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// Grab the first number.
fn first(&self) -> i32 { self.0 }
// Grab the last number.
fn last(&self) -> i32 { self.1 }
}
无需在上层指定类型,使用者暗含了类型:
rust
fn difference<C: Contains>(container: &C) -> i32 {
container.last() - container.first()
}
2
rust
use std::ops::Sub;
#[derive(Debug, PartialEq)]
struct Point<T> {
x: T,
y: T,
}
// 用三种方法填空: 其中两种使用默认的泛型参数,另外一种不使用
impl __ {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Point {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
fn main() {
assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 },
Point { x: 1, y: 3 });
println!("Success!")
}
这题目是第一个我没做出来,然后不得不求助答案的题目... ...这三种语法是逐渐变简单的,第一种是最复杂的,核心是impl T
,但是对T
进行Sub
的特征限定,而且输出必须也是T
(否则后面x
和y
无法相减);其次Sub
的类型需要是Point
,最后Point
也需要泛型参数。
rust
impl<T: Sub<Output = T>> Sub<Point<T>> for Point<T> {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Point {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
由于Point
就是Self
,所以这就是第二种语法:
rust
impl<T: Sub<Output = T>> Sub<Self> for Point<T> {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Point {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
而Sub
本身的泛型参数是存在默认值的,就是Self
,因此这里可以继续省略:
rust
impl<T: Sub<Output = T>> Sub for Point<T> {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Point {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
3
rust
trait Pilot {
fn fly(&self) -> String;
}
trait Wizard {
fn fly(&self) -> String;
}
struct Human;
impl Pilot for Human {
fn fly(&self) -> String {
String::from("This is your captain speaking.")
}
}
impl Wizard for Human {
fn fly(&self) -> String {
String::from("Up!")
}
}
impl Human {
fn fly(&self) -> String {
String::from("*waving arms furiously*")
}
}
fn main() {
let person = Human;
assert_eq!(__, "This is your captain speaking.");
assert_eq!(__, "Up!");
assert_eq!(__, "*waving arms furiously*");
println!("Success!")
}
这里涉及到一个多继承的问题,即一个类同时实现了两个特征,其中各有一个签名相同的方法,类本身也有一个签名相同的方法,需要加限定符号区分。
rust
fn main() {
let person = Human;
assert_eq!(Pilot::fly(&person), "This is your captain speaking.");
assert_eq!(Wizard::fly(&person), "Up!");
assert_eq!(Human::fly(&person), "*waving arms furiously*");
println!("Success!")
}
当然,自己调用自己的方法还有更简单,也是更常见的写法:
rust
assert_eq!(person.fly(), "*waving arms furiously*");
C++里也存在多继承的问题,虽然就我的工作经验来看很少出现,比如下面的代码:
cpp
struct P1
{
virtual void get()
{
cout << "P1 get" << endl;
}
};
struct P2
{
virtual void get()
{
cout << "P2 get" << endl;
}
};
struct Child: P1, P2
{
// virtual void get()
// {
// cout << "Child get" << endl;
// }
};
int main()
{
Child child;
child.get();
}
这里的get
就会出现歧义的问题。解决办法也是加上限定符:
cpp
int main()
{
Child child;
child.P1::get();
child.P2::get();
}
直接像Rust那么写肯定是不行的,这种直接用双冒号开头的写法,在C++中是静态方法(static)的特权。
4
rust
trait Person {
fn name(&self) -> String;
}
// Person 是 Student 的 supertrait .
// 实现 Student 需要同时实现 Person.
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
// CompSciStudent (computer science student) 是 Programmer
// 和 Student 的 subtrait. 实现 CompSciStudent 需要先实现这两个 supertraits.
trait CompSciStudent: Programmer + Student {
fn git_username(&self) -> String;
}
fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
format!(
"My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
student.name(),
student.university(),
student.fav_language(),
student.git_username()
)
}
struct CSStudent {
name: String,
university: String,
fav_language: String,
git_username: String
}
// 为 CSStudent 实现所需的特征
impl ...
fn main() {
let student = CSStudent {
name: "Sunfei".to_string(),
university: "XXX".to_string(),
fav_language: "Rust".to_string(),
git_username: "sunface".to_string()
};
// 填空
println!("{}", comp_sci_student_greeting(__));
}
逐个实现特征即可,不过Rust居然不能一次实现所有的特征(感觉这些特征方法也可以写在同一个里)
rust
impl Person for CSStudent {
fn name(&self) -> String {
self.name.clone()
}
}
impl Student for CSStudent{
fn university(&self) -> String{
self.university.clone()
}
}
impl Programmer for CSStudent {
fn fav_language(&self) -> String {
self.fav_language.clone()
}
}
impl CompSciStudent for CSStudent{
fn git_username(&self) -> String{
self.git_username.clone()
}
}
5
rust
use std::fmt;
// 定义一个 newtype `Pretty`
impl fmt::Display for Pretty {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.0.clone() + ", world")
}
}
fn main() {
let w = Pretty("hello".to_string());
println!("w = {}", w);
}
这玩意让我觉得孤儿规则没什么意义。。。
rust
use std::fmt;
// 定义一个 newtype `Pretty`
struct Pretty(String);
impl fmt::Display for Pretty {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.0.clone() + ", world")
}
}
fn main() {
let w = Pretty("hello".to_string());
println!("w = {}", w);
}