bigywater.tech
Rust · 38

impl Trait 参数与返回值

impl Trait 是日常最常写的泛型语法糖。这篇验证它在参数位置和返回位置各自的真实含义、一个让人意外的限制(只能返回一种类型)、edition 2024 对生命周期捕获的改动,以及它不能做什么。

E0308
返回位置的 impl Trait 不能在分支里返回不同类型
E0700
edition 2021:返回的 impl Trait 捕获了未声明的生命周期(2024 默认通过)
E0107
impl Trait 参数不能用 turbofish 指定

参数位置

实测 · 本机执行输出
  via_impl(调用 len_impl::<i32>)与 via_generic(调用 len_generic::<i32>)的汇编:
     b	__RINvCskmIYF9N4tvP_4u38b11len_genericlEB2_
    别名行: `_via_impl = _via_generic`(两个包装函数被合并,说明它们调用的实例化函数一样)

返回位置

实测 · 本机执行输出
  源码:
      1 | fn evens(limit: u32) -> impl Iterator<Item = u32> { (0..limit).filter(|n| n % 2 == 0) }
      2 | fn adder(k: i32) -> impl Fn(i32) -> i32 { move |x| x + k }
      3 | fn main() {
      4 |     println!("{:?}", evens(10).collect::<Vec<_>>());
      5 |     let f = adder(5); println!("adder(5)(3) = {}", f(3));
      6 |     println!("evens 返回的具体类型名:{}", std::any::type_name_of_val(&evens(1)));
      7 | }
  编译:成功
  运行(退出码 0):
    [0, 2, 4, 6, 8]
    adder(5)(3) = 8
    evens 返回的具体类型名:core::iter::adapters::filter::Filter<core::ops::range::Range<u32>, u38c::evens::{{closure}}>
  源码:
      1 | use std::fmt::Display;
      2 | fn pick(flag: bool) -> impl Display { if flag { 1 } else { "one" } }
      3 | fn main() { println!("{}", pick(true)); }
  编译:失败
    error[E0308]: `if` and `else` have incompatible types
     --> <实验目录>/src/u38d.rs:2:60
      |
    2 | fn pick(flag: bool) -> impl Display { if flag { 1 } else { "one" } }
      |                                                 -          ^^^^^ expected integer, found `&str`
      |                                                 |
      |                                                 expected because of this
      |
    help: you could change the return type to be a boxed trait object
      |
    2 - fn pick(flag: bool) -> impl Display { if flag { 1 } else { "one" } }
    2 + fn pick(flag: bool) -> Box<dyn Display> { if flag { 1 } else { "one" } }
      |
    help: if you change the return type to expect trait objects, box the returned expressions
      |
    2 | fn pick(flag: bool) -> impl Display { if flag { Box::new(1) } else { Box::new("one") } }
      |                                                 +++++++++ +          +++++++++     +
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0308`.
  源码:
      1 | use std::fmt::Display;
      2 | fn pick(flag: bool) -> Box<dyn Display> { if flag { Box::new(1) } else { Box::new("one") } }
      3 | fn main() { println!("{} {}", pick(true), pick(false)); }
  编译:成功
  运行(退出码 0):
    1 one

需要运行时选择不同类型时用 trait 对象,见Trait Object 与动态分发。

生命周期的捕获

实测 · 本机执行输出
  --edition 2021:
  源码:
      1 | fn doubled(v: &Vec<u32>) -> impl Iterator<Item = u32> { v.iter().map(|x| x * 2) }
      2 | fn main() { let v = vec![1, 2, 3]; println!("{:?}", doubled(&v).collect::<Vec<_>>()); }
  编译:失败
    error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that does not appear in bounds
     --> <实验目录>/src/u38f2021.rs:1:57
      |
    1 | fn doubled(v: &Vec<u32>) -> impl Iterator<Item = u32> { v.iter().map(|x| x * 2) }
      |               ---------     -------------------------   ^^^^^^^^^^^^^^^^^^^^^^^
      |               |             |
      |               |             opaque type defined here
      |               hidden type `Map<std::slice::Iter<'_, u32>, {closure@<实验目录>/src/u38f2021.rs:1:70: 1:73}>` captures the anonymous lifetime defined here
      |
    help: add a `use<...>` bound to explicitly capture `'_`
      |
    1 | fn doubled(v: &Vec<u32>) -> impl Iterator<Item = u32> + use<'_> { v.iter().map(|x| x * 2) }
      |                                                       +++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0700`.
  --edition 2024:
  编译:成功
  运行(退出码 0):
    [2, 4, 6]
  源码:
      1 | fn doubled<'a>(v: &'a Vec<u32>) -> impl Iterator<Item = u32> + 'a { v.iter().map(|x| x * 2) }
      2 | fn main() { let v = vec![1, 2, 3]; println!("{:?}", doubled(&v).collect::<Vec<_>>()); }
  编译:成功
  运行(退出码 0):
    [2, 4, 6]
  (上面这段是 2021 里的写法:显式加 + 'a 就可以了)

edition 2024 在 if let 临时值上的另一处变化见if let 与 let else。

不能指定

实测 · 本机执行输出
  源码:
      1 | fn f(x: impl std::fmt::Display) -> String { x.to_string() }
      2 | fn main() { println!("{}", f::<i32>(5)); }
  编译:失败
    error[E0107]: function takes 0 generic arguments but 1 generic argument was supplied
     --> <实验目录>/src/u38g.rs:2:28
      |
    2 | fn main() { println!("{}", f::<i32>(5)); }
      |                            ^------- help: remove the unnecessary generics
      |                            |
      |                            expected 0 generic arguments
      |
    note: function defined here, with 0 generic parameters
     --> <实验目录>/src/u38g.rs:1:4
      |
    1 | fn f(x: impl std::fmt::Display) -> String { x.to_string() }
      |    ^
      = note: `impl Trait` cannot be explicitly specified as a generic argument
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0107`.

分步演示

impl Trait
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。第三节分别用 --edition=2021 与 --edition=2024 编译同一份源码
    • 没有验证:use<..> 精确捕获语法、返回 impl Future(async fn 的展开)、impl Trait 在 trait 方法返回位置(RPITIT)
    京ICP备2026067502号-1京公网安备11010502063487号