• [Rust笔记] 我也浅谈【泛型参数】的【晚·绑定late bound】


    我也浅谈【泛型参数】的【晚·绑定late bound

    名词解释

    为了减少对正文内容理解的歧义,我们先统一若干术语的名词解释:

    • 泛型项:

      • 要么,泛型函数generic function

      • 要么,泛型类型generic type(比如,泛型结构体)。

    • 泛型参数:

      • 要么,泛型·类型·参数generic type parameter

      • 要么,泛型·生命周期·参数generic lifetime parameter

    • 泛型参数限定条件:

      见下图吧,实在不容易文字描述

    • 63356e87be394639b80e9be77ee36835.png

      • 要么,trait bounds

      • 要么,lifetime bounds

    • 高阶·生命周期·限定条件higher-ranked lifetime bounds

      ad8331e24e477a15d4a83bccf875f033.png

      • 语法:for<'a>

      • 功能:描述【高阶函数】内【闭包】类型【形参 / 返回值】里【形参 / 返回值】的生命周期。文字描述得绕儿,直接看下图吧,一图抵千词。

    • FSTFixed Size Type

    【泛型参数】的【绑定】是【编译时】概念

    首先,无论是【早·绑定】还是【晚·绑定】,【泛型参数-绑定】都是发生在编译阶段,而不是运行期间。

    • 只不过【泛型参数·早·绑定】是发生在【单态化monomorphize过程中的【泛型项】定义位置

    • 而【泛型参数·晚·绑定】是发生在【单态化monomorphize之后的【泛型项】调用位置(比如,函数调用)。

    所以,【泛型参数】的【早/晚·绑定】是一个纯编译时概念,还是馁馁的【编译时-抽象】和零运行时(抽象)成本。

    区分【泛型参数·早/晚·绑定】的标准

    其次,区分【泛型参数】是【早·绑定】还是【晚·绑定】的标准就是

    • 若在【rustc单态化monomorphize】期间,就能推断出【泛型参数】具体“值”,那么该【泛型参数】就是【早·绑定】。

    • 若在【rustc单态化monomorphize】之后,还需评估【泛型项】的调用方式,才能确定【泛型参数】具体“值”,那么该【泛型参数】就是【晚·绑定】。

    推断【泛型参数】绑定值的方式

    接着,被【早·绑定】的【泛型参数】

    • 既可·由编译器自动推断 [例程1]

    • 也可·徒手编写TurboFish调用语句显示地指定 [例程1]

    再次,被【晚·绑定】的【泛型参数】

    • 仅能·由编译器自动推断 [例程3]

    • 不可·由TurboFish调用语句显示地指定 [例程2]

    【泛型参数 - 晚·绑定】不支持TurboFish语法

    原因是【TurboFish调用语句·展开】与【泛型参数 - 晚·绑定】有两项不同:

    • 第一,执行时间点不同

      • TurboFish调用语句是在【单态化monomorphize过程中被展开的。

      • 【泛型参数 - 晚·绑定】则是发生在【单态化monomorphize之后。此时,TurboFish调用语句的源码已经不存在了(— 之前已经被展开了)。

    • 第二,执行位置不同

      • 【已知项】:函数的引用类型【实参】的生命周期

      • 【未知项】:函数的引用类型【返回值】的生命周期

      • 有点抽象,那举个例子:展开【泛型项】调用位置上的let array = iterator.collect::<Vec<u8>>();语句会导致,在【单态化monomorphize】之后,在Iterator::collect()成员方法的定义位置多出来一个fn collect(self) -> Vec<u8>的新成员方法定义。由此可见,最终的修改项还是落在了【泛型项】定义位置的codegen代码上。

      • 由此得出一个结论:TurboFish语法调用语句·等同于·【泛型参数 - 早·绑定

      • 编译器对TurboFish调用语句的【展开】处理会回过头来对【泛型项】定义位置的代码产生影响。即,【单态化】会生成更多的代码 — 这类由编译器生成的代码被称为codegen

      • 而由【泛型参数·晚·绑定】确定【泛型参数】【实参】并不会导致在【泛型项】定义位置有新的codegen被生成。这是一个纯“调用位置”的,由【已知项】推断【未知项】的行为。其中,

    通用规则

    先直接记结论吧。以后,再慢慢体会底层逻辑。

    • 【泛型·类型·参数】都是【早·绑定】的。例如,在给【函数指针】赋值前,必须先明确【泛型·类型·参数】的具体“值”。

      fn m<T>() {}let m1 = m::<u8>; // 赋值函数指针,得先确定泛型类型参数`T`的实参值`u8`。m1(); // 经由【函数指针】调用函数就没有机会再显示地指定【泛型参数】值了。
    • 【泛型函数】的【泛型·生命周期·参数】都是【晚·绑定】,

      • 【泛型函数】是一个【成员方法】且引用了由其所属【泛型类型】(比如,结构体)声明的另一个【泛型·生命周期·参数】(有点绕儿,看 [例程3])。于是,该【泛型函数】使用的这个【生命周期·参数】就是【早·绑定】的。

      • lifetime bound出现。即,【泛型·生命周期·参数】正被另一个【泛型·生命周期·参数】所限定(比如,<'a, 'b> where 'a: 'b)。有点绕儿,看 [例程4]。于是,该【泛型函数】的这两个【泛型·生命周期·参数】(限定的·与·被限定的)皆都是【早·绑定】。

      • 要么,忽略【泛型·生命周期·参数】的存在。别说你没写过这样的代码,可能仅只是没有认真思考为什么可以这样。

        1. fn m<'a>(name: &'a str) -> &'a str {name}let m1 = m; // 'a 的生命周期参数被直接无视了。let r = m1("test"); // 函数被调用了才知道其实参的`lifetime`是`static`
        2. // 和其返回值的`lifetime`也是`static`
      • 要么,使用【高阶·生命周期·限定条件higher-ranked lifetime bounds】显示地标注待定的【泛型·生命周期·参数】

        1. fn m<'a>(name: &'a str) -> &'a str {name}// `for<'a>`语法表示`'a`生命周期参数的实参待定。let m1: for<'a> fn(&'a str) -> &'a str = m; // 函数指针写法let r = m1("test"); // 函数被调用了才知道其实参的`lifetime`是`static`
        2. // 和其返回值的`lifetime`也是`static`// 对于不嫌麻烦的你,没准【闭包`trait`写法】也是一个选择。let m2: Box<dyn for<'a> Fn(&'a str) -> &'a str> = Box::new(m);let r = m2("test");
      • 因为函数不被调用,就不知其【实参】的真实生命周期。而【泛型函数】【生命周期·参数】的关键作用就是以【实参】生命周期为“已知量",推断【返回值】生命周期的"未知量"。特别是,当一个函数同时有多个·引用类型·形参输入和·引用类型·返回值输出时,【泛型·生命周期·参数】就必须被声明和使用,否则编译错误。

      • 在【函数指针】赋值中,

      • 两个【早·绑定】的例外

    • 【泛型类型】的【泛型·生命周期·参数】都是【早·绑定】,

      • 【泛型类型】的【泛型参数】声明包含了【高阶·生命周期·限定条件higher-ranked lifetime bound】 [例程5]。

      • 因为明确了类型,也就明确了如何实例化该类型。而【泛型类型】【生命周期·参数】的关键作用就是以该类型【实例】的生命周期为“已知量”,推断它的·引用类型·字段值生命周期的“未知量”。

      • 一个【晚·绑定】的例外

    写在最后的补充

    • 没有【限定条件】的【泛型参数】,编译器会自动给其安排缺省bound

      • 就【泛型·类型·参数】而言,编译器会自动给该【泛型参数】添加Sized缺省trait bound。即,<T: Sized>。所以,【泛型·类型·参数】一定都是FST的。

      • 就【泛型lifetime参数】而言,编译器会认为该【泛型参数】生存期 >= 【泛型项】生存期。

    • 【生命周期】参数也是【泛型参数】。

    我总结了lifetime bound限定条件的四句实用口诀

    • 左长,右短 — 被限定项总比限定项更能“活” <'a, 'b> where 'a: 'b则有'a >= 'b

    • 留长,返短 — 函数【引用类型·返回值】的生命周期总是对齐”最短命“【入参】的生命周期 [例程6] fn test<'a, 'b>(a: &'a str, b: &'b str) -> &'b str where 'a: 'b

    • 内长,外短 — 引用的引用。越是外层的引用,其生命周期就越短<'a, 'b> where 'a: 'b则有&'b &'a i32。而,&'a &'b i32会导致编译错误。

    • 'static最”命长“ — 它馁馁地命长于任何被显示声明的生命周期参数'a

    至此,我已经倾其所有领会内容。希望对读者理解【泛型参数 - 绑定】有所帮助。我希望看官老爷们评论、转发、点赞 — 图名不图利。咱们共同进步。

  • 相关阅读:
    “一人企业”核心是需要一个”独一无二“的核心技能,然后把它产品化
    元宇宙带来的游戏变革会是怎样的?
    springcloud之Eureka
    Windows 下使用bpg 图片 - 查看,转换,预览
    LeetCode 每日一题 2022/8/15-2022/8/21
    第一次汇报yandex广告数据时,应该展示哪些数据
    数字孪生论文阅读笔记【1.2】
    golang基于errgroup实现并发调用
    上海市计算机学会竞赛平台2023年7月月赛丙组排列排序
    Vue-(6)
  • 原文地址:https://blog.csdn.net/u012067469/article/details/125580005