色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

淺聊泛型常量參數

jf_wN0SrCdH ? 來源:Rust語言中文社區 ? 2023-03-15 13:46 ? 次閱讀

淺聊泛型常量參數Const Generic

引題

最近有網友私信我討論:若使用規則宏編譯時統計token tree序列的長度,如何繞開由宏遞歸自身局限性造成的:

  • 被統計序列不能太長
  • 編譯延時顯著拖長
的問題。然后,就貼出了如下的一段例程代碼1:

	
		fn main() { macro_rules! count_tts { ($_a:tt $($tail: tt)*) => { 1_usize + count_tts!($($tail)*) }; () => { 0_usize }; } assert_eq!(10, count_tts!(,,,,,,,,,,)); }
嚯!這段短小精悍的代碼餒餒地演示了Incremental TT Muncher設計模式的精髓。贊!
首先,宏遞歸深度是有極限的(默認是128層)。所以,若每次遞歸僅新統計一個token,那么被統計序列的最大長度自然不能超過128。否則,突破上限,編譯失敗! 其次,尾遞歸優化運行時壓縮函數調用棧的技術手段,卻做不到編譯時抑制調用棧的膨脹。所以,巧用#![recursion_limit="…"]元屬性強制調高宏遞歸深度上限很可能會導致編譯器棧溢出。 由此,如果僅追求快速繞過問題,那最經濟實惠的作法是:在每次宏遞歸期間,統計幾個token例程2(而不是一次一個)。從算數上,將總遞歸次數降下來,和使計數更長的token tree序列成為可能。

	
		fn main() { // 這代碼看著就“傻乎乎的”。 macro_rules! count_tts { ($_a: tt $_b: tt $_c: tt $_d: tt $_e: tt $_f: tt // 一次遞歸統計 6 個。 $($tail: tt)*) => { 6_usize + count_tts!($($tail)*) }; ($_a: tt $_b: tt $_c: // 一次遞歸統計 3 個。 tt $($tail: tt)*) => { 3_usize + count_tts!($($tail)*) }; ($_a: tt // 一次遞歸統計 1 個。 $($tail: tt)*) => { 1_usize + count_tts!($($tail)*) }; () => { 0_usize }; // 結束了,統計完成 } println!("token tree 個數是 {}", count_tts!(,,,,,,,,,,)); }
		倘若要標本兼治地解決問題,將遞歸調用變形成循環結構才是正途,因為循環本身不會增加調用棧的深度。這涵蓋了:
  1. 宏循環結構token tree序列變形成數組字面量
  2. 常量函數調用觸發編譯器對數組字面量的類型推導
  3. 因為rust數組在編譯時明確大小,所以數組長度被編入了數據類型定義內。
  4. 泛型常量參數從數據類型定義中提取出數組長度值,并作為序列長度返回。
全套操作被統稱為Array length設計模式。它帶入了兩個技術難點:
  1. 如何觸發rustc對數組字面量的類型推導,和從推導結果中提取出數組長度信息
  2. 如何撇開遞歸的“吐吞模式”(即,吐Incremental TT Muncher和吞Push-down Accumulation),僅憑宏循環結構,將token tree序列變形成為數組字面量。
第一個難點源于自rustc 1.51才穩定的新語言特性“泛型常量參數Const Generic”。而第二個難點的解決就多樣化了
  • 要么,采用“循環替換設計模式Repetition Replacement(RR)
  • 要么,啟用試驗階段語言特性“元變量表達式Meta-variable Expression
接下來,它們會被逐一地講解分析。

泛型常量參數

rustc 1.51+起,【泛型常量參數 】允許泛型項(類或函數)接受常量值或常量表達式為泛型參數。根據泛型常量參數出現的位置不同(請見下圖例程3),它又細分為
  • 泛型常量參數的
  • 泛型常量參數的

66a8dafc-c2f3-11ed-bfe3-dac502259ad0.png

下文分別將它們簡稱為“泛型常量形參”與“泛型常量實參”。

泛型參數的分類

于是,已知的泛型參數就包含有三種類型:66c7c264-c2f3-11ed-bfe3-dac502259ad0.png

泛型常量參數的數據類型

可用作【泛型常量參數】的數據類型包括兩類:
  • 整數數字類型:u8u16u32u64u128usizei8i16i32i64i128isize
  • 數字化類型:charbool

泛型常量參數的“怪癖”

首先,就“同名沖突”而言,若【泛型常量形參】與【類型】同名并作為另一個泛型項的泛型參數實參,那么rustc會優先將該泛型參數當作類型帶入程序上下文。多數情況下,這會造成程序編譯失敗。解決方案是使用表達式{...}包裝泛型常量參數,以向rustc標注此同名參數是泛型常量參數而不是類型名例程4。

66d7369a-c2f3-11ed-bfe3-dac502259ad0.png

其次,就“聲明和使用”而言,泛型常量參允許僅被聲明,而不被使用。對另兩種泛型參數而言,這卻會導致編譯失敗例程5。66e81a0a-c2f3-11ed-bfe3-dac502259ad0.png 最后,泛型常量參的trait實現不會因為窮舉了全部備選形參值而自動過渡給泛型常量參。如下例程6(左),即便泛型項struct Foo顯示地給泛型常量B每個可能的(參)值true / false都實現的同一個trait Bar,編譯器也不會“聰明地”歸納出該trait Bar已經被此泛型項的泛型常量參充分實現了,因為編譯器可不會“歸納法”方法論(不確定chatGPT是否能做到?)。相反,每個參上的trait實現都被視作不相關的個例。正確地作法是:泛型項必須明確地給泛型常量參實現trait例程7(右)。

66fe7d04-c2f3-11ed-bfe3-dac502259ad0.png

泛型常量參數的適用位置

泛型常量參數原則上可出現于常量項適用的全部位置,包括但不限于:
  • 運行時求值表達式#1— 模糊了編譯時泛型參數與運行時值之間的界限。
  • 常量表達式#2
  • 關聯常量#2
  • 關聯類型#3
  • 結構體字段 或 綁定變量的數據類型#4。比如,編譯時參數化數組長度。
  • 結構體字段 或 綁定變量的值#5
上述列表內的#1 ~ #5,可在下面例程8源碼內找到對應的代碼行。

	use rand::{thread_rng, Rng}; fn main() { fn foo1<const N1: usize>(input: usize) { // 在泛型函數內,泛型常量參數的形參可用于 let sum = 1 + N1 * input; // #1 運行時求值的表達式 let foo = Foo([input; N1]); // #5 結構體字段的值 let arr: [usize; N1] = [input; N1]; // #4 綁定變量的數據類型 —— 編譯時參數化數組長度 // #5 綁定變量的值 println!("運行時表達式:{sum}, 元組結構體: {foo:?}, 數組: {arr:?}"); } trait Trait<const N2: usize> { const CONST: usize = N2 + 4; // #2 關聯常量 + 常量表達式 type Output; } #[derive(Debug)] struct Foo<const N3: usize>( [usize; N3] // #4 結構體字段的數據類型 —— 編譯時參數化數組長度 ); impl<const N4: usize> Trait for Foo { type Output = [usize; N4]; // #3 關聯類型 —— 編譯時參數化數組長度 } let mut rng = thread_rng(); foo1::<2>(rng.gen_range::<usize, _>(1..10)); foo1::<{1 + 2}>(rng.gen_range::<usize, _>(1..10)); const K: usize = 3; foo1::(rng.gen_range::<usize, _>(1..10)); foo1::<{K * 2}>(rng.gen_range::<usize, _>(1..10)); }

泛型常量參數的不適用位置

首先,泛型常量參不能:
  • 定義常量靜態變量,無論是作為類型定義的一部分,還是值#1
  • 隔層使用。比如,在子函數內引用由外層函數聲明的泛型常量#2。除了子函數,該規則也適用于在函數體內定義的
    • 結構體#3
    • 類型別名#4
上述列表內的#1 ~ #4,可在下面例程9源碼內找到對應的代碼行。

	fn main() { fn outer<const N: usize>(input: usize) { // 泛型常量參數【不】可用于函數體內的 // #1 常量定義 // - 既不能定義類型 const BAD_CONST: [usize; N] = [1; N]; // - 既不能定義值 const BAD_CONST: usize = 1 + N; // #1 靜態變量定義 // - 既不能定義類型 static BAD_STATIC: [usize; N] = [N + 1; N]; // - 既不能定義值 static BAD_STATIC: usize = 1 + N; fn inner(bad_arg: [usize; N]) { // #2 在子函數內不能引用外層函數聲明的 // 泛型常量形參,無論是將其作為 // 變量類型,還是常量值。 let bad_value = N * 2; } // #3 結構體內也不能引用外層函數聲明的 // 泛型常量形參。 struct BadStruct([usize; N]); // 相反,需要給結構體重新聲明泛型常量參數 struct BadStruct<const N: usize>([usize; N]); // #4 類型別名內不能引用外層函數聲明的 // 泛型常量形參。 type BadAlias = [usize; N]; // 相反,需要給類型別名重新聲明泛型常量參數 type BadAlias<const N: usize> = [usize; N]; } }
		其次,泛型常量接受包含了泛型常量參的常量表達式例程10。

6703d380-c2f3-11ed-bfe3-dac502259ad0.png

但是,泛型常量參并不拒絕接受
  • 獨立泛型常量參例程11
  • 包含泛型常量參的普通常量表達式例程12 題外話,不確定這么翻譯該術語lookahead是否正確。我借鑒了 @余晟 在《精通正則表達式》一書中對此詞條的譯文。
    • 被用作泛型常量參的常量表達式必須被包裝在表達式{...}內。避免編譯器在解析AST過程中陷入正向環視lookahead的無限循環中。

數組重復表達式與泛型常量參數

數組重復表達式[repeat_operand; length_operand]是數組字面量的一種形式。在數組重復表達式中,泛型常量形參
  • 雖然既可用于左repeat操作數位置,也可用于右length操作數位置例程13
  • 但在右length操作數位置上,泛型常量參只能獨立出現例程14,而不能作為常量表達式的一部分 —— 等同于泛型常量參的限制。

67250e88-c2f3-11ed-bfe3-dac502259ad0.png

回到序列計數問題

類似于解析幾何中的“投影”方法,通過將高維物體(token tree序列)投影于低維平面(數組),以主動舍棄若干信息項(每個token的具體值與數據類型)為代價,突出該物體更有價值的信息內容(序列長度),便可降低從復雜結構中摘取特定關注信息項的合計復雜度。這套“降維算法”帶來的啟發就是:
  1. 既然讀取數組長度是簡單的,那為什么不先將token tree序列變形為數組呢?
    1. 答:投影token tree序列為數組
  2. 既然token tree序列的內容細節不被關注,那為什么還要糾結于數組的數據類型與填充值呢?全部充滿unit type豈不快哉!
    1. 再答:投影token tree序列為單位數組[(); N]。僅數組長度對我們有價值。
于是,循環替換設計模式Repetition Replacement(RR)與元變量表達式${ignore(識別符名)}都是被用來改善【宏循環結構】的使用體驗,以允許Rustacean對循環結構中的循環重復項“宣而不用” —— 既遍歷token tree序列,同時又棄掉每個具體的token元素,最后還生成一個等長的單位數組[(); N]。否則,未被使用的“循環重復項”會導致error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth的編譯錯誤。
  • 循環替換設計模式Repetition Replacement(RR)是以在宏循環體內插入一層“空轉”宏調用,消費掉consuming未被使用的“循環重復項”例程15
  • 元變量表達式${ignore(識別符名)}是前者的語法糖,允許Rustacean少敲幾行代碼。但因為元變量表達式是試驗性的新語法,所以需要開啟對應的feature-gate開關#![feature(macro_metavar_expr)]才能被使用。例程16
然后,常量函數調用和函數參觸發編譯器對單位數組字面量的類型推導。 接著,泛型常量參從被推導出的數據類型定義內提取出數組長度信息。 最后,將泛型常量參作為常量函數的返回值輸出。 上圖,一圖抵千詞。

673fcdfe-c2f3-11ed-bfe3-dac502259ad0.png

結束語

除了前文提及的【宏遞歸法】與Array Length設計模式,統計token tree序列長度還有
  • Slice Length設計模式
    • 原理類似Array Length,但調用數組字面量的pub const fn len(&self) -> usize成員方法讀取長度值(而不是依賴類型推導和泛型參數提取)。
  • 枚舉計數法
    • 規則宏將token tree序列變形為“枚舉類”(而不是數組字面量),再由最后一個枚舉值的分辨因子discriminant值加1獲得序列長度。
    • 但,缺點也明顯。比如,token tree序列內不能包含rust語法關鍵字與重復項。
  • 比特計數法
    • 典型的算法優化。從數學層面,將程序復雜度從O(n)降到O(log(n))。有些復雜,回頭單獨寫一篇文章分享之。
【規則宏】與【泛型參數】皆是rust編程語言提供的業務功能開發利器。宏循環結構與泛型常量參數僅只是它們的冰山一角。此文既匯總分享與網友的討論成果,也對此話題拋磚引玉。希望有機會與路過的神仙哥哥和仙女妹妹們更深入地交流相關技術知識點與實踐經驗。 審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 參數
    +關注

    關注

    11

    文章

    1864

    瀏覽量

    32568
  • 函數
    +關注

    關注

    3

    文章

    4353

    瀏覽量

    63290
  • 編譯
    +關注

    關注

    0

    文章

    666

    瀏覽量

    33210
  • 數據類型
    +關注

    關注

    0

    文章

    236

    瀏覽量

    13694

原文標題:淺聊泛型常量參數

文章出處:【微信號:Rust語言中文社區,微信公眾號:Rust語言中文社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    詳解Rust的

    所有的編程語言都致力于將重復的任務簡單化,并為此提供各種各樣的工具。在 Rust 中,(generics)就是這樣一種工具,它是具體類型或其它屬性的抽象替代。在編寫代碼時,我們可以直接描述
    發表于 11-12 09:08 ?1131次閱讀

    Go語言常量的聲明

    在 Go 語言中, 常量 表示的是固定的值,常量表達式的值在編譯期進行計算,常量的值不可以修改。例如:3 、 Let's go 、 3.14 等等。常量中的數據類型只可以是
    發表于 07-20 15:24 ?473次閱讀

    Golang的使用

    眾所周知很多語言的function 中都支持 key=word 關鍵字參數, 但 golang 是不支持的, 我們可以利用去簡單的實現。
    發表于 08-16 12:24 ?338次閱讀

    Java的背景和作用

    Java的背景和作用 Java是Java編程語言中的一個特性,引入的目的是為了增強代
    的頭像 發表于 09-20 14:30 ?1202次閱讀
    Java<b class='flag-5'>泛</b><b class='flag-5'>型</b>的背景和作用

    labview連接mongdb問題,找到不.NET類中的

    有沒有人用labview連接mongodb數據庫的?已下載mongodb的c#驅動,利用labview中的.net控件調用相關函數,但是驅動中有部分函數在類中, labview能調用c#中的
    發表于 04-08 13:38

    冒泡排序法的實現

    冒泡排序法的實現,自用筆記!
    發表于 01-20 07:22

    C語言教程之數值常量的使用

    C語言教程之數值常量的使用,很好的C語言資料,快來學習吧。
    發表于 04-22 11:06 ?0次下載

    iOS中關于的解析

    文章圍繞這五點: 1. 是什么 2. 為什么要用 3. 怎么用 4.
    發表于 09-25 10:01 ?0次下載

    聊聊java實現的原理與好處

    摘要: 和C++以模板來實現靜多態不同,Java基于運行時支持選擇了,兩者的實現原理大相庭徑。C++可以支持基本類型作為模板參數,Java卻只能接受類作為
    發表于 09-27 16:50 ?0次下載

    51單片機C語言的變量和常量如何區分常量的詳細資料說明

    程序運行過程中不能改變值的量,而變量是可以在程序運行過程中不斷變化的量。變量的定義可以使用所有C51編譯器支持的數據類型,而常量的數據類型只有整型、浮點、字符、字符串和位標量。這
    發表于 07-24 17:37 ?0次下載
    51單片機C語言的變量和<b class='flag-5'>常量</b>如何區分<b class='flag-5'>常量</b>的詳細資料說明

    Java的工作原理和案例

    是Java語言一個非常重要的概念,在Java集合類框架中被廣泛應用。在介紹之前先看一個例子。
    的頭像 發表于 07-01 10:14 ?2741次閱讀

    淺談指針常量常量指針

    這節課我們來講一講指針常量常量指針。
    的頭像 發表于 02-21 09:27 ?1166次閱讀

    C語言的常量-2

    在C語言中,字符常量是最特別的一種常量。他的特別之處在于我們需要對其使用指定的定界符對其進行限制。定界符為 ‘’ 。字符常量可以分為兩種
    的頭像 發表于 02-21 15:02 ?743次閱讀
    C語言的<b class='flag-5'>常量</b>-2

    稱重傳感器數量和量程原理

    稱重傳感器數量和量程原理
    的頭像 發表于 12-20 17:01 ?1680次閱讀
    <b class='flag-5'>淺</b><b class='flag-5'>聊</b>稱重傳感器數量和量程原理

    C語言是否支持編程?

    C語言是否支持編程?毫無疑問,答案是不支持。
    的頭像 發表于 10-16 10:02 ?799次閱讀
    主站蜘蛛池模板: 91综合久久久久婷婷 | 亚洲伊人精品综合在合线 | 久久re视频这里精品一本到99 | 香蕉59tv视频 | 亚洲国产免费观看视频 | 国产主播福利一区二区 | 疯狂做受XXXX高潮欧美日本 | 簧片高清在线观看 | 成年免费大片黄在线观看岛国 | 中文字幕AV在线一二三区 | 中文字幕乱码在线人视频 | 国产精品久久久久影院免费 | 久久只有这里有精品4 | 亚洲中文字幕乱码熟女在线 | 亚洲精品不卡在线 | 美女脱18以下禁止看免费 | 国产精品悠悠久久人妻精品 | 永久精品免费影院在线观看网站 | 成人精品视频在线观看 | 亚洲精品色情婷婷在线播放 | 大胸美女被c | 偷窥国产亚洲免费视频 | chinese耄耋70老太性 | 噜噜噜狠狠夜夜躁 | 灰原哀被啪漫画禁漫 | 亚洲 国产 日韩 欧美 在线 | 男人女人边摸边吃奶边做 | 国产精品1卡二卡三卡四卡乱码 | 最新国产精品福利2020 | 青草在线观看视频 | s8sp视频高清在线播放 | 狠狠爱亚洲五月婷婷av | 人妻夜夜爽99麻豆AV | a毛片基地免费全部视频 | 男人的天堂色 | 国产人妻人伦精品836700 | 九九热最新视频 | 善良的小峓子2在钱中文版女主角 | 国产女人91精品嗷嗷嗷嗷 | 亚洲 欧美 国产 伦 综合 | 久久网站视频 |