第19章 newtype 和 Sized
newtype 和 Sized — Rust By Practice
内容来源 · Rust By Practice / Rust 语言实战
原文链接: https://practice-rust-zh.beatai.org/newtype-sized.html
newtype 和 Sized
Newtype
孤儿规则要求:只有当 trait 或类型其一属于当前 crate 时,才能为类型实现该 trait。
newtype 模式 通过定义一个 元组结构体 新类型,帮助绕开这一限制。
- 🌟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| use std::fmt;
/* 定义 Wrapper 类型 */
__;
// Display 是外部 trait
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
// Vec 是外部类型,不能直接为 Vec 实现外部 trait Display
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
|
- 🌟 隐藏原类型的方法
1
2
3
4
5
6
7
8
9
10
11
| /* 让代码通过编译 */
struct Meters(u32);
fn main() {
let i: u32 = 2;
assert_eq!(i.pow(2), 4);
let n = Meters(i);
// `pow` 定义在 u32 上,直接调用会报错
assert_eq!(n.pow(2), 4);
}
|
- 🌟🌟
newtype 能在编译期保证传入的值类型正确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| /* 让它工作 */
struct Years(i64);
struct Days(i64);
impl Years {
pub fn to_days(&self) -> Days {
Days(self.0 * 365)
}
}
impl Days {
pub fn to_years(&self) -> Years {
Years(self.0 / 365)
}
}
// 检查年龄(单位:年),必须接收 Years
fn old_enough(age: &Years) -> bool {
age.0 >= 18
}
fn main() {
let age = Years(5);
let age_days = age.to_days();
println!("Old enough {}", old_enough(&age));
println!("Old enough {}", old_enough(&age_days));
}
|
- 🌟🌟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| use std::ops::Add;
use std::fmt::{self, format};
struct Meters(u32);
impl fmt::Display for Meters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "There are still {} meters left", self.0)
}
}
impl Add for Meters {
type Output = Self;
fn add(self, other: Meters) -> Self {
Self(self.0 + other.0)
}
}
fn main() {
let d = calculate_distance(Meters(10), Meters(20));
assert_eq!(format!("{}",d), "There are still 30 meters left");
}
/* 实现 calculate_distance */
fn calculate_distance
|
类型别名 (Type alias)
类型别名可以提升代码可读性。
1
2
3
4
5
6
7
8
9
10
11
| type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -> Thunk {
// --snip--
}
|
1
| type Result<T> = std::result::Result<T, std::io::Error>;
|
与 newtype 不同,类型别名不会生成新类型,因此下面的代码合法:
1
2
3
4
5
6
| type Meters = u32;
let x: u32 = 5;
let y: Meters = 5;
println!("x + y = {}", x + y);
|
- 🌟
1
2
3
4
5
6
7
8
9
10
11
12
| enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}
/* 填空 */
__
fn main() {
// 可以用别名访问枚举值,避免冗长名字
let x = Operations::Add;
}
|
- 🌟🌟 Rust 有一些保留的别名,其中一个可在
impl 中使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
| enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}
impl VeryVerboseEnumOfThingsToDoWithNumbers {
fn run(&self, x: i32, y: i32) -> i32 {
match self {
__::Add => x + y,
__::Subtract => x - y,
}
}
}
|
DST 与不定长类型
概念较复杂,这里不展开,可参考 The Book。
- 🌟🌟🌟 动态长度数组属于 DST,无法直接使用
1
2
3
4
5
6
7
8
9
| /* Make it work with const generics */
fn my_function(n: usize) -> [u32; usize] {
[123; n]
}
fn main() {
let arr = my_function();
println!("{:?}",arr);
}
|
- 🌟🌟 Slice 本身是 unsized,但它的引用是定长的。
1
2
3
4
5
6
| /* Make it work with slice references */
fn main() {
let s: str = "Hello there!";
let arr: [u8] = [1, 2, 3];
}
|
- 🌟🌟 Trait 也是 unsized 类型
1
2
3
4
5
6
| /* 用两种方式让它工作 */
use std::fmt::Display;
fn foobar(thing: Display) {}
fn main() {
}
|