bigywater.tech
Rust · 43

Associated Type

关联类型和泛型参数都能让 trait「带一个类型」,但它们的语义不同:关联类型是「这个类型实现 trait 时确定的一个类型」。这篇用编译器的报错把两者的区别摆出来,再看约束里的几种写法和标准库里最常见的三个关联类型。

E0119
关联类型的 trait 对同一类型只能实现一次
E0283
泛型参数的 trait 多次实现后调用要标注类型
Item / Target / Output
标准库里最常见的三个关联类型

定义与使用

实测 · 本机执行输出
  源码:
      1 | trait Container {
      2 |     type Item;
      3 |     fn first(&self) -> Option<&Self::Item>;
      4 |     fn size(&self) -> usize;
      5 | }
      6 | struct Words(Vec<String>);
      7 | struct Nums([i32; 3]);
      8 | impl Container for Words { type Item = String; fn first(&self) -> Option<&String> { self.0.first() } fn size(&self) -> usize { self.0.len() } }
      9 | impl Container for Nums { type Item = i32; fn first(&self) -> Option<&i32> { self.0.first() } fn size(&self) -> usize { 3 } }
     10 | fn show<C: Container>(c: &C) -> String where C::Item: std::fmt::Debug { format!("size={} first={:?}", c.size(), c.first()) }
     11 | fn main() {
     12 |     println!("{}", show(&Words(vec!["a".into(), "b".into()])));
     13 |     println!("{}", show(&Nums([7, 8, 9])));
     14 | }
  编译:成功
  运行(退出码 0):
    size=2 first=Some("a")
    size=3 first=Some(7)

关联类型 vs 泛型参数

实测 · 本机执行输出
  源码:
      1 | trait From2<T> { fn conv(&self) -> T; }
      2 | struct N(i32);
      3 | impl From2<i64> for N { fn conv(&self) -> i64 { self.0 as i64 } }
      4 | impl From2<String> for N { fn conv(&self) -> String { self.0.to_string() } }
      5 | fn main() {
      6 |     let n = N(5);
      7 |     let a: i64 = n.conv();
      8 |     let b: String = n.conv();
      9 |     println!("{a} {b}");
     10 | }
  编译:成功
  运行(退出码 0):
    5 5
  编译:失败
    error[E0283]: type annotations needed
     --> <实验目录>/src/u43c.rs:5:31
      |
    5 | fn main() { let n = N(5); let x = n.conv(); }
      |                               ^     ---- type must be known at this point
      |
    note: multiple `impl`s satisfying `N: From2<_>` found
     --> <实验目录>/src/u43c.rs:3:1
      |
    3 | impl From2<i64> for N { fn conv(&self) -> i64 { self.0 as i64 } }
      | ^^^^^^^^^^^^^^^^^^^^^
    4 | impl From2<String> for N { fn conv(&self) -> String { self.0.to_string() } }
      | ^^^^^^^^^^^^^^^^^^^^^^^^
    help: consider giving `x` an explicit type
      |
    5 | fn main() { let n = N(5); let x: /* Type */ = n.conv(); }
      |                                ++++++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0283`.
  源码:
      1 | trait Container { type Item; fn get(&self) -> Self::Item; }
      2 | struct N(i32);
      3 | impl Container for N { type Item = i64; fn get(&self) -> i64 { self.0 as i64 } }
      4 | impl Container for N { type Item = String; fn get(&self) -> String { self.0.to_string() } }
      5 | fn main() {}
  编译:失败
    error[E0119]: conflicting implementations of trait `Container` for type `N`
     --> <实验目录>/src/u43d.rs:4:1
      |
    3 | impl Container for N { type Item = i64; fn get(&self) -> i64 { self.0 as i64 } }
      | -------------------- first implementation here
    4 | impl Container for N { type Item = String; fn get(&self) -> String { self.0.to_string() } }
      | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `N`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0119`.

约束里的写法

实测 · 本机执行输出
  源码:
      1 | use std::fmt::Debug;
      2 | use std::ops::Add;
      3 | fn sum_all<I: Iterator<Item = u32>>(it: I) -> u32 { it.sum() }
      4 | fn debug_all<I>(it: I) -> Vec<String> where I: Iterator, I::Item: Debug { it.map(|x| format!("{:?}", x)).collect() }
      5 | fn add_twice<T: Add<Output = T> + Copy>(a: T) -> T { a + a }
      6 | fn output_name<T: Add>(_: T) -> &'static str { std::any::type_name::<<T as Add>::Output>() }
      7 | fn main() {
      8 |     println!("{}", sum_all(vec![1u32, 2, 3].into_iter()));
      9 |     println!("{:?}", debug_all("a b".split(' ')));
     10 |     println!("{} {}", add_twice(2), add_twice(1.5));
     11 |     println!("i32 + i32 的 Output 类型:{}", output_name(1i32));
     12 | }
  编译:成功
  运行(退出码 0):
    6
    ["\"a\"", "\"b\""]
    4 3
    i32 + i32 的 Output 类型:i32
  编译:失败
    error[E0277]: cannot add a float to an integer
     --> <实验目录>/src/u43f.rs:1:23
      |
    1 | fn main() { let x = 1 + 2.0; }
      |                       ^ no implementation for `{integer} + {float}`
      |
      = help: the trait `Add<{float}>` is not implemented for `{integer}`
      = help: the following other types implement trait `Add<Rhs>`:
                `&f128` implements `Add<f128>`
                `&f128` implements `Add`
                `&f16` implements `Add<f16>`
                `&f16` implements `Add`
                `&f32` implements `Add<f32>`
                `&f32` implements `Add`
                `&f64` implements `Add<f64>`
                `&f64` implements `Add`
              and 56 others
    
    …(共 21 行)

标准库中的例子

实测 · 本机执行输出
  源码:
      1 | use std::any::type_name;
      2 | use std::ops::{Add, Deref};
      3 | fn item_of<I: Iterator>(_: &I) -> &'static str { type_name::<I::Item>() }
      4 | fn target_of<D: Deref>(_: &D) -> &'static str { type_name::<D::Target>() }
      5 | fn output_of<A: Add<B>, B>(_: &A, _: &B) -> &'static str { type_name::<A::Output>() }
      6 | fn main() {
      7 |     println!("vec::IntoIter<u8>::Item   = {}", item_of(&vec![1u8].into_iter()));
      8 |     println!("Box<String>::Target       = {}", target_of(&Box::new(String::new())));
      9 |     println!("String::Target            = {}", target_of(&String::new()));
     10 |     println!("String + &str 的 Output   = {}", output_of(&String::new(), &"x"));
     11 | }
  编译:成功
  运行(退出码 0):
    vec::IntoIter<u8>::Item   = u8
    Box<String>::Target       = alloc::string::String
    String::Target            = str
    String + &str 的 Output   = alloc::string::String

更高一层的泛型关联类型(GAT)见Generic Associated Type;迭代器的 Item 见Iterator Trait。

分步演示

关联类型
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息为原样的 rustc 1.98.1 输出
    • 没有验证:关联类型的默认值(不稳定)、关联类型与关联常量的组合、dyn Trait 的对象类型写法的细节
    京ICP备2026067502号-1京公网安备11010502063487号