[{"data":1,"prerenderedAt":19142},["ShallowReactive",2],{"stack-rust-crate-in-action":3},{"id":4,"title":5,"author":6,"body":7,"category":6,"date":19134,"description":67,"extension":19135,"image":6,"meta":19136,"navigation":463,"path":19137,"recommend":6,"seo":19138,"stem":19139,"tags":19140,"updated":6,"__hash__":19141},"stack\u002Fblog\u002Fstack\u002Frust-crate-in-action.md","Rust 常用库实战教程",null,{"type":8,"value":9,"toc":19042},"minimark",[10,22,25,30,56,61,134,141,168,186,190,363,376,378,382,386,389,403,407,425,860,864,1181,1207,1213,1216,1441,1445,1448,1463,1534,1540,1542,1546,1550,1561,1564,1620,1624,1638,2146,2158,2162,2176,2667,2695,2706,2708,2712,2716,2735,2741,2755,2759,2943,2947,3371,3387,3391,3611,3617,3621,3926,3937,3995,4008,4012,4790,4803,4805,4809,4813,4816,4837,4841,5174,5178,5675,5681,5685,5948,5952,6246,6248,6252,6256,6263,6289,6302,6306,6830,6840,6844,7939,7943,8874,8878,9030,9032,9036,9040,9055,9069,9075,9079,9861,9864,9920,9924,10072,10074,10078,10082,10099,10105,10109,10128,10297,10352,10362,10366,10376,10395,10810,10815,10822,10835,10854,10856,10860,10864,10870,10884,10888,11543,11549,11553,12034,12050,12052,12056,12070,12094,12802,12822,12824,12828,12842,12846,13669,13673,13847,13861,13863,13867,13870,13884,14874,14888,14890,14894,14898,14908,14922,15438,15463,15469,15578,15582,15609,15611,15615,15619,15634,15657,15661,16530,16536,16540,16573,16587,16589,16593,16596,16600,16609,16653,16657,16666,16722,16726,16829,16833,16848,16975,16979,16988,17147,17151,17160,17241,17245,17254,17494,17498,17507,17719,17723,17732,17844,17848,17940,17942,17946,17950,18346,18350,18365,18427,18431,18507,18511,18526,18857,18859,18863,18868,18893,18898,19029,19038],[11,12,13],"blockquote",{},[14,15,16,17,21],"p",{},"面向已有 Rust 基础语法、想快速上手工程开发的读者。\n每个库都包含：",[18,19,20],"strong",{},"它解决什么问题、为什么要用它、最常见的用法 + 可运行的小案例","。\n文中代码基于各库 2025–2026 年的主流版本（serde 1.x \u002F tokio 1.x \u002F reqwest 0.12 \u002F axum 0.8 \u002F clap 4.x \u002F rand 0.9 \u002F sqlx 0.8 等）。",[23,24],"hr",{},[26,27,29],"h2",{"id":28},"第-0-章-开始之前cargo-与依赖管理","第 0 章 开始之前：Cargo 与依赖管理",[14,31,32,33,36,37,44,45,50,51,55],{},"Rust 的库叫 ",[18,34,35],{},"crate","，统一发布在 ",[38,39,43],"a",{"href":40,"rel":41},"https:\u002F\u002Fcrates.io",[42],"nofollow","crates.io","，文档统一托管在 ",[38,46,49],{"href":47,"rel":48},"https:\u002F\u002Fdocs.rs",[42],"docs.rs","。\n看某个库的用法，直接访问 ",[52,53,54],"code",{},"https:\u002F\u002Fdocs.rs\u002F\u003C库名>"," 即可，这是 Rust 生态最重要的习惯。",[57,58,60],"h3",{"id":59},"_01-添加依赖","0.1 添加依赖",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-bash shiki shiki-themes rose-pine-dawn rose-pine-dawn rose-pine-moon","# 现代方式（推荐）：用 cargo add，自动写入最新兼容版本\ncargo add serde --features derive\ncargo add tokio --features full\ncargo add reqwest --features json\n","bash","",[52,69,70,83,104,119],{"__ignoreMap":67},[71,72,75,79],"span",{"class":73,"line":74},"line",1,[71,76,78],{"class":77},"sGUtj","#",[71,80,82],{"class":81},"sNkb5"," 现代方式（推荐）：用 cargo add，自动写入最新兼容版本\n",[71,84,86,90,94,97,101],{"class":73,"line":85},2,[71,87,89],{"class":88},"svtxk","cargo",[71,91,93],{"class":92},"sKSG5"," add",[71,95,96],{"class":92}," serde",[71,98,100],{"class":99},"sP2eF"," --features",[71,102,103],{"class":92}," derive\n",[71,105,107,109,111,114,116],{"class":73,"line":106},3,[71,108,89],{"class":88},[71,110,93],{"class":92},[71,112,113],{"class":92}," tokio",[71,115,100],{"class":99},[71,117,118],{"class":92}," full\n",[71,120,122,124,126,129,131],{"class":73,"line":121},4,[71,123,89],{"class":88},[71,125,93],{"class":92},[71,127,128],{"class":92}," reqwest",[71,130,100],{"class":99},[71,132,133],{"class":92}," json\n",[14,135,136,137,140],{},"也可以手动编辑 ",[52,138,139],{},"Cargo.toml","：",[62,142,146],{"className":143,"code":144,"language":145,"meta":67,"style":67},"language-toml shiki shiki-themes rose-pine-dawn rose-pine-dawn rose-pine-moon","[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\ntokio = { version = \"1\", features = [\"full\"] }\nreqwest = { version = \"0.12\", features = [\"json\"] }\n","toml",[52,147,148,153,158,163],{"__ignoreMap":67},[71,149,150],{"class":73,"line":74},[71,151,152],{},"[dependencies]\n",[71,154,155],{"class":73,"line":85},[71,156,157],{},"serde = { version = \"1\", features = [\"derive\"] }\n",[71,159,160],{"class":73,"line":106},[71,161,162],{},"tokio = { version = \"1\", features = [\"full\"] }\n",[71,164,165],{"class":73,"line":121},[71,166,167],{},"reqwest = { version = \"0.12\", features = [\"json\"] }\n",[14,169,170,173,174,177,178,181,182,185],{},[18,171,172],{},"为什么强调 features？"," Rust crate 普遍把非核心功能放在 feature 后面（比如 serde 的 ",[52,175,176],{},"derive","、tokio 的 ",[52,179,180],{},"full","），默认不开启。这样编译时只编译你真正用到的代码，",[18,183,184],{},"编译更快、产物更小","。这是 Rust「零成本抽象」哲学在依赖管理上的体现：不为用不到的东西付出代价。",[57,187,189],{"id":188},"_02-常用-cargo-命令","0.2 常用 Cargo 命令",[62,191,193],{"className":64,"code":192,"language":66,"meta":67,"style":67},"cargo new my_app        # 新建二进制项目\ncargo new my_lib --lib  # 新建库项目\ncargo build             # 调试构建（快，无优化）\ncargo build --release   # 发布构建（慢，有优化）\ncargo run               # 构建并运行\ncargo check             # 只检查不生成二进制，改代码时最快\ncargo test              # 运行测试\ncargo doc --open        # 生成并打开本项目（含依赖）的文档\ncargo update            # 在语义化版本允许范围内升级依赖\ncargo add \u002F cargo rm    # 增删依赖\ncargo tree              # 查看依赖树，排查版本冲突\n",[52,194,195,211,229,242,257,271,284,298,314,328,350],{"__ignoreMap":67},[71,196,197,199,202,205,208],{"class":73,"line":74},[71,198,89],{"class":88},[71,200,201],{"class":92}," new",[71,203,204],{"class":92}," my_app",[71,206,207],{"class":77},"        #",[71,209,210],{"class":81}," 新建二进制项目\n",[71,212,213,215,217,220,223,226],{"class":73,"line":85},[71,214,89],{"class":88},[71,216,201],{"class":92},[71,218,219],{"class":92}," my_lib",[71,221,222],{"class":99}," --lib",[71,224,225],{"class":77},"  #",[71,227,228],{"class":81}," 新建库项目\n",[71,230,231,233,236,239],{"class":73,"line":106},[71,232,89],{"class":88},[71,234,235],{"class":92}," build",[71,237,238],{"class":77},"             #",[71,240,241],{"class":81}," 调试构建（快，无优化）\n",[71,243,244,246,248,251,254],{"class":73,"line":121},[71,245,89],{"class":88},[71,247,235],{"class":92},[71,249,250],{"class":99}," --release",[71,252,253],{"class":77},"   #",[71,255,256],{"class":81}," 发布构建（慢，有优化）\n",[71,258,260,262,265,268],{"class":73,"line":259},5,[71,261,89],{"class":88},[71,263,264],{"class":92}," run",[71,266,267],{"class":77},"               #",[71,269,270],{"class":81}," 构建并运行\n",[71,272,274,276,279,281],{"class":73,"line":273},6,[71,275,89],{"class":88},[71,277,278],{"class":92}," check",[71,280,238],{"class":77},[71,282,283],{"class":81}," 只检查不生成二进制，改代码时最快\n",[71,285,287,289,292,295],{"class":73,"line":286},7,[71,288,89],{"class":88},[71,290,291],{"class":92}," test",[71,293,294],{"class":77},"              #",[71,296,297],{"class":81}," 运行测试\n",[71,299,301,303,306,309,311],{"class":73,"line":300},8,[71,302,89],{"class":88},[71,304,305],{"class":92}," doc",[71,307,308],{"class":99}," --open",[71,310,207],{"class":77},[71,312,313],{"class":81}," 生成并打开本项目（含依赖）的文档\n",[71,315,317,319,322,325],{"class":73,"line":316},9,[71,318,89],{"class":88},[71,320,321],{"class":92}," update",[71,323,324],{"class":77},"            #",[71,326,327],{"class":81}," 在语义化版本允许范围内升级依赖\n",[71,329,331,333,335,338,341,344,347],{"class":73,"line":330},10,[71,332,89],{"class":88},[71,334,93],{"class":92},[71,336,337],{"class":92}," \u002F",[71,339,340],{"class":92}," cargo",[71,342,343],{"class":92}," rm",[71,345,346],{"class":77},"    #",[71,348,349],{"class":81}," 增删依赖\n",[71,351,353,355,358,360],{"class":73,"line":352},11,[71,354,89],{"class":88},[71,356,357],{"class":92}," tree",[71,359,294],{"class":77},[71,361,362],{"class":81}," 查看依赖树，排查版本冲突\n",[11,364,365],{},[14,366,367,368,371,372,375],{},"经验：写代码时多用 ",[52,369,370],{},"cargo check","，秒级反馈；需要运行时再 ",[52,373,374],{},"cargo run","。",[23,377],{},[26,379,381],{"id":380},"第-1-章-serde-serde_json序列化与反序列化","第 1 章 serde \u002F serde_json：序列化与反序列化",[57,383,385],{"id":384},"_11-它解决什么问题","1.1 它解决什么问题",[14,387,388],{},"程序里的结构体和外部世界的 JSON\u002FTOML\u002FYAML 之间需要互相转换。serde 是 Rust 生态事实上的标准，几乎所有库（reqwest、axum、sqlx、config 解析……）都建立在它之上。",[14,390,391,394,395,398,399,402],{},[18,392,393],{},"为什么要用 derive 而不是手写？"," 手写序列化代码冗长且容易和结构体定义脱节（改了字段忘了改解析逻辑）。",[52,396,397],{},"#[derive(Serialize, Deserialize)]"," 在编译期自动生成转换代码，",[18,400,401],{},"结构体定义即契约","，改一处即可，且零运行时开销。",[57,404,406],{"id":405},"_12-最小案例","1.2 最小案例",[62,408,410],{"className":143,"code":409,"language":145,"meta":67,"style":67},"[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n",[52,411,412,416,420],{"__ignoreMap":67},[71,413,414],{"class":73,"line":74},[71,415,152],{},[71,417,418],{"class":73,"line":85},[71,419,157],{},[71,421,422],{"class":73,"line":106},[71,423,424],{},"serde_json = \"1\"\n",[62,426,430],{"className":427,"code":428,"language":429,"meta":67,"style":67},"language-rust shiki shiki-themes rose-pine-dawn rose-pine-dawn rose-pine-moon","use serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct User {\n    name: String,\n    age: u8,\n    email: Option\u003CString>, \u002F\u002F 可空字段用 Option\n}\n\nfn main() -> serde_json::Result\u003C()> {\n    \u002F\u002F 结构体 -> JSON 字符串\n    let u = User { name: \"小明\".into(), age: 20, email: None };\n    let s = serde_json::to_string(&u)?;\n    println!(\"{s}\"); \u002F\u002F {\"name\":\"小明\",\"age\":20,\"email\":null}\n\n    \u002F\u002F 美化输出\n    println!(\"{}\", serde_json::to_string_pretty(&u)?);\n\n    \u002F\u002F JSON 字符串 -> 结构体\n    let raw = r#\"{\"name\":\"小红\",\"age\":18,\"email\":\"a@b.com\"}\"#;\n    let v: User = serde_json::from_str(raw)?;\n    println!(\"{v:?}\");\n    Ok(())\n}\n","rust",[52,431,432,459,465,492,503,518,530,555,560,564,591,599,654,688,717,722,730,766,771,779,794,826,846,855],{"__ignoreMap":67},[71,433,434,437,440,443,447,450,453,456],{"class":73,"line":74},[71,435,436],{"class":99},"use",[71,438,96],{"class":439},"sn0A_",[71,441,442],{"class":99},"::",[71,444,446],{"class":445},"sp1GA","{",[71,448,449],{"class":439},"Deserialize",[71,451,452],{"class":445},",",[71,454,455],{"class":439}," Serialize",[71,457,458],{"class":445},"};\n",[71,460,461],{"class":73,"line":85},[71,462,464],{"emptyLinePlaceholder":463},true,"\n",[71,466,467,470,473,476,479,481,484,486,489],{"class":73,"line":106},[71,468,469],{"class":445},"#[",[71,471,176],{"class":472},"sdIFE",[71,474,475],{"class":445},"(",[71,477,478],{"class":439},"Serialize",[71,480,452],{"class":445},[71,482,483],{"class":439}," Deserialize",[71,485,452],{"class":445},[71,487,488],{"class":439}," Debug",[71,490,491],{"class":445},")]\n",[71,493,494,497,500],{"class":73,"line":121},[71,495,496],{"class":99},"struct",[71,498,499],{"class":439}," User",[71,501,502],{"class":445}," {\n",[71,504,505,509,512,515],{"class":73,"line":259},[71,506,508],{"class":507},"s00_H","    name",[71,510,511],{"class":99},":",[71,513,514],{"class":439}," String",[71,516,517],{"class":445},",\n",[71,519,520,523,525,528],{"class":73,"line":273},[71,521,522],{"class":507},"    age",[71,524,511],{"class":99},[71,526,527],{"class":439}," u8",[71,529,517],{"class":445},[71,531,532,535,537,540,543,546,549,552],{"class":73,"line":286},[71,533,534],{"class":507},"    email",[71,536,511],{"class":99},[71,538,539],{"class":439}," Option",[71,541,542],{"class":445},"\u003C",[71,544,545],{"class":439},"String",[71,547,548],{"class":445},">,",[71,550,551],{"class":77}," \u002F\u002F",[71,553,554],{"class":81}," 可空字段用 Option\n",[71,556,557],{"class":73,"line":300},[71,558,559],{"class":445},"}\n",[71,561,562],{"class":73,"line":316},[71,563,464],{"emptyLinePlaceholder":463},[71,565,566,569,572,575,578,581,583,586,589],{"class":73,"line":330},[71,567,568],{"class":99},"fn",[71,570,571],{"class":88}," main",[71,573,574],{"class":445},"()",[71,576,577],{"class":99}," ->",[71,579,580],{"class":439}," serde_json",[71,582,442],{"class":99},[71,584,585],{"class":439},"Result",[71,587,588],{"class":445},"\u003C()>",[71,590,502],{"class":445},[71,592,593,596],{"class":73,"line":352},[71,594,595],{"class":77},"    \u002F\u002F",[71,597,598],{"class":81}," 结构体 -> JSON 字符串\n",[71,600,602,605,608,611,613,616,619,621,624,627,630,633,636,638,641,643,646,648,651],{"class":73,"line":601},12,[71,603,604],{"class":99},"    let",[71,606,607],{"class":507}," u",[71,609,610],{"class":99}," =",[71,612,499],{"class":439},[71,614,615],{"class":445}," {",[71,617,618],{"class":507}," name",[71,620,511],{"class":99},[71,622,623],{"class":92}," \"小明\"",[71,625,626],{"class":99},".",[71,628,629],{"class":88},"into",[71,631,632],{"class":445},"(),",[71,634,635],{"class":507}," age",[71,637,511],{"class":99},[71,639,640],{"class":88}," 20",[71,642,452],{"class":445},[71,644,645],{"class":507}," email",[71,647,511],{"class":99},[71,649,650],{"class":439}," None",[71,652,653],{"class":445}," };\n",[71,655,657,659,662,664,666,668,671,673,676,679,682,685],{"class":73,"line":656},13,[71,658,604],{"class":99},[71,660,661],{"class":507}," s",[71,663,610],{"class":99},[71,665,580],{"class":439},[71,667,442],{"class":99},[71,669,670],{"class":88},"to_string",[71,672,475],{"class":445},[71,674,675],{"class":99},"&",[71,677,678],{"class":507},"u",[71,680,681],{"class":445},")",[71,683,684],{"class":99},"?",[71,686,687],{"class":445},";\n",[71,689,691,694,696,699,701,704,707,709,712,714],{"class":73,"line":690},14,[71,692,693],{"class":88},"    println!",[71,695,475],{"class":445},[71,697,698],{"class":92},"\"",[71,700,446],{"class":445},[71,702,703],{"class":92},"s",[71,705,706],{"class":445},"}",[71,708,698],{"class":92},[71,710,711],{"class":445},");",[71,713,551],{"class":77},[71,715,716],{"class":81}," {\"name\":\"小明\",\"age\":20,\"email\":null}\n",[71,718,720],{"class":73,"line":719},15,[71,721,464],{"emptyLinePlaceholder":463},[71,723,725,727],{"class":73,"line":724},16,[71,726,595],{"class":77},[71,728,729],{"class":81}," 美化输出\n",[71,731,733,735,737,739,742,744,746,748,750,753,755,757,759,761,763],{"class":73,"line":732},17,[71,734,693],{"class":88},[71,736,475],{"class":445},[71,738,698],{"class":92},[71,740,741],{"class":445},"{}",[71,743,698],{"class":92},[71,745,452],{"class":445},[71,747,580],{"class":439},[71,749,442],{"class":99},[71,751,752],{"class":88},"to_string_pretty",[71,754,475],{"class":445},[71,756,675],{"class":99},[71,758,678],{"class":507},[71,760,681],{"class":445},[71,762,684],{"class":99},[71,764,765],{"class":445},");\n",[71,767,769],{"class":73,"line":768},18,[71,770,464],{"emptyLinePlaceholder":463},[71,772,774,776],{"class":73,"line":773},19,[71,775,595],{"class":77},[71,777,778],{"class":81}," JSON 字符串 -> 结构体\n",[71,780,782,784,787,789,792],{"class":73,"line":781},20,[71,783,604],{"class":99},[71,785,786],{"class":507}," raw",[71,788,610],{"class":99},[71,790,791],{"class":92}," r#\"{\"name\":\"小红\",\"age\":18,\"email\":\"a@b.com\"}\"#",[71,793,687],{"class":445},[71,795,797,799,802,804,806,808,810,812,815,817,820,822,824],{"class":73,"line":796},21,[71,798,604],{"class":99},[71,800,801],{"class":507}," v",[71,803,511],{"class":99},[71,805,499],{"class":439},[71,807,610],{"class":99},[71,809,580],{"class":439},[71,811,442],{"class":99},[71,813,814],{"class":88},"from_str",[71,816,475],{"class":445},[71,818,819],{"class":507},"raw",[71,821,681],{"class":445},[71,823,684],{"class":99},[71,825,687],{"class":445},[71,827,829,831,833,835,837,840,842,844],{"class":73,"line":828},22,[71,830,693],{"class":88},[71,832,475],{"class":445},[71,834,698],{"class":92},[71,836,446],{"class":445},[71,838,839],{"class":92},"v:?",[71,841,706],{"class":445},[71,843,698],{"class":92},[71,845,765],{"class":445},[71,847,849,852],{"class":73,"line":848},23,[71,850,851],{"class":439},"    Ok",[71,853,854],{"class":445},"(())\n",[71,856,858],{"class":73,"line":857},24,[71,859,559],{"class":445},[57,861,863],{"id":862},"_13-常用字段属性","1.3 常用字段属性",[62,865,867],{"className":427,"code":866,"language":429,"meta":67,"style":67},"#[derive(Serialize, Deserialize)]\nstruct Article {\n    #[serde(rename = \"id\")]            \u002F\u002F JSON 里的字段名与 Rust 字段名不同\n    article_id: u64,\n\n    #[serde(default)]                   \u002F\u002F JSON 缺这个字段时用 Default 值而不是报错\n    tags: Vec\u003CString>,\n\n    #[serde(default = \"default_status\")]\u002F\u002F 缺省值用指定函数\n    status: String,\n\n    #[serde(skip_serializing)]          \u002F\u002F 只反序列化，不序列化（如密码）\n    password: String,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")] \u002F\u002F None 时不输出该字段，而不是输出 null\n    avatar: Option\u003CString>,\n\n    #[serde(flatten)]                   \u002F\u002F 把内嵌结构体的字段拍平到本层\n    extra: std::collections::HashMap\u003CString, serde_json::Value>,\n}\n\nfn default_status() -> String { \"draft\".into() }\n",[52,868,869,885,894,922,934,938,957,974,978,1002,1013,1017,1036,1047,1051,1074,1089,1093,1111,1146,1150,1154],{"__ignoreMap":67},[71,870,871,873,875,877,879,881,883],{"class":73,"line":74},[71,872,469],{"class":445},[71,874,176],{"class":472},[71,876,475],{"class":445},[71,878,478],{"class":439},[71,880,452],{"class":445},[71,882,483],{"class":439},[71,884,491],{"class":445},[71,886,887,889,892],{"class":73,"line":85},[71,888,496],{"class":99},[71,890,891],{"class":439}," Article",[71,893,502],{"class":445},[71,895,896,899,902,904,907,910,913,916,919],{"class":73,"line":106},[71,897,898],{"class":445},"    #[",[71,900,901],{"class":472},"serde",[71,903,475],{"class":445},[71,905,906],{"class":472},"rename ",[71,908,909],{"class":99},"=",[71,911,912],{"class":92}," \"id\"",[71,914,915],{"class":445},")]",[71,917,918],{"class":77},"            \u002F\u002F",[71,920,921],{"class":81}," JSON 里的字段名与 Rust 字段名不同\n",[71,923,924,927,929,932],{"class":73,"line":121},[71,925,926],{"class":507},"    article_id",[71,928,511],{"class":99},[71,930,931],{"class":439}," u64",[71,933,517],{"class":445},[71,935,936],{"class":73,"line":259},[71,937,464],{"emptyLinePlaceholder":463},[71,939,940,942,944,946,949,951,954],{"class":73,"line":273},[71,941,898],{"class":445},[71,943,901],{"class":472},[71,945,475],{"class":445},[71,947,948],{"class":472},"default",[71,950,915],{"class":445},[71,952,953],{"class":77},"                   \u002F\u002F",[71,955,956],{"class":81}," JSON 缺这个字段时用 Default 值而不是报错\n",[71,958,959,962,964,967,969,971],{"class":73,"line":286},[71,960,961],{"class":507},"    tags",[71,963,511],{"class":99},[71,965,966],{"class":439}," Vec",[71,968,542],{"class":445},[71,970,545],{"class":439},[71,972,973],{"class":445},">,\n",[71,975,976],{"class":73,"line":300},[71,977,464],{"emptyLinePlaceholder":463},[71,979,980,982,984,986,989,991,994,996,999],{"class":73,"line":316},[71,981,898],{"class":445},[71,983,901],{"class":472},[71,985,475],{"class":445},[71,987,988],{"class":472},"default ",[71,990,909],{"class":99},[71,992,993],{"class":92}," \"default_status\"",[71,995,915],{"class":445},[71,997,998],{"class":77},"\u002F\u002F",[71,1000,1001],{"class":81}," 缺省值用指定函数\n",[71,1003,1004,1007,1009,1011],{"class":73,"line":330},[71,1005,1006],{"class":507},"    status",[71,1008,511],{"class":99},[71,1010,514],{"class":439},[71,1012,517],{"class":445},[71,1014,1015],{"class":73,"line":352},[71,1016,464],{"emptyLinePlaceholder":463},[71,1018,1019,1021,1023,1025,1028,1030,1033],{"class":73,"line":601},[71,1020,898],{"class":445},[71,1022,901],{"class":472},[71,1024,475],{"class":445},[71,1026,1027],{"class":472},"skip_serializing",[71,1029,915],{"class":445},[71,1031,1032],{"class":77},"          \u002F\u002F",[71,1034,1035],{"class":81}," 只反序列化，不序列化（如密码）\n",[71,1037,1038,1041,1043,1045],{"class":73,"line":656},[71,1039,1040],{"class":507},"    password",[71,1042,511],{"class":99},[71,1044,514],{"class":439},[71,1046,517],{"class":445},[71,1048,1049],{"class":73,"line":690},[71,1050,464],{"emptyLinePlaceholder":463},[71,1052,1053,1055,1057,1059,1062,1064,1067,1069,1071],{"class":73,"line":719},[71,1054,898],{"class":445},[71,1056,901],{"class":472},[71,1058,475],{"class":445},[71,1060,1061],{"class":472},"skip_serializing_if ",[71,1063,909],{"class":99},[71,1065,1066],{"class":92}," \"Option::is_none\"",[71,1068,915],{"class":445},[71,1070,551],{"class":77},[71,1072,1073],{"class":81}," None 时不输出该字段，而不是输出 null\n",[71,1075,1076,1079,1081,1083,1085,1087],{"class":73,"line":724},[71,1077,1078],{"class":507},"    avatar",[71,1080,511],{"class":99},[71,1082,539],{"class":439},[71,1084,542],{"class":445},[71,1086,545],{"class":439},[71,1088,973],{"class":445},[71,1090,1091],{"class":73,"line":732},[71,1092,464],{"emptyLinePlaceholder":463},[71,1094,1095,1097,1099,1101,1104,1106,1108],{"class":73,"line":768},[71,1096,898],{"class":445},[71,1098,901],{"class":472},[71,1100,475],{"class":445},[71,1102,1103],{"class":472},"flatten",[71,1105,915],{"class":445},[71,1107,953],{"class":77},[71,1109,1110],{"class":81}," 把内嵌结构体的字段拍平到本层\n",[71,1112,1113,1116,1118,1121,1123,1126,1128,1131,1133,1135,1137,1139,1141,1144],{"class":73,"line":773},[71,1114,1115],{"class":507},"    extra",[71,1117,511],{"class":99},[71,1119,1120],{"class":439}," std",[71,1122,442],{"class":99},[71,1124,1125],{"class":439},"collections",[71,1127,442],{"class":99},[71,1129,1130],{"class":439},"HashMap",[71,1132,542],{"class":445},[71,1134,545],{"class":439},[71,1136,452],{"class":445},[71,1138,580],{"class":507},[71,1140,442],{"class":99},[71,1142,1143],{"class":439},"Value",[71,1145,973],{"class":445},[71,1147,1148],{"class":73,"line":781},[71,1149,559],{"class":445},[71,1151,1152],{"class":73,"line":796},[71,1153,464],{"emptyLinePlaceholder":463},[71,1155,1156,1158,1161,1163,1165,1167,1169,1172,1174,1176,1178],{"class":73,"line":828},[71,1157,568],{"class":99},[71,1159,1160],{"class":88}," default_status",[71,1162,574],{"class":445},[71,1164,577],{"class":99},[71,1166,514],{"class":439},[71,1168,615],{"class":445},[71,1170,1171],{"class":92}," \"draft\"",[71,1173,626],{"class":99},[71,1175,629],{"class":88},[71,1177,574],{"class":445},[71,1179,1180],{"class":445}," }\n",[14,1182,1183,1194,1195,1198,1199,1202,1203,1206],{},[18,1184,1185,1186,1189,1190,1193],{},"为什么 ",[52,1187,1188],{},"Option"," 字段还要加 ",[52,1191,1192],{},"skip_serializing_if","？"," 默认 ",[52,1196,1197],{},"None"," 会序列化成 ",[52,1200,1201],{},"\"avatar\": null","，但很多前端\u002F接口约定「没有就不给这个 key」。这个属性让输出更干净，也避免对方把 ",[52,1204,1205],{},"null"," 和「不存在」混为一谈。",[57,1208,1210,1211],{"id":1209},"_14-处理不确定结构的-jsonvalue","1.4 处理不确定结构的 JSON：",[52,1212,1143],{},[14,1214,1215],{},"爬第三方接口时，字段经常不稳定，不值得定义完整结构体：",[62,1217,1219],{"className":427,"code":1218,"language":429,"meta":67,"style":67},"use serde_json::Value;\n\nlet raw = r#\"{\"code\":0,\"data\":{\"users\":[{\"name\":\"a\"},{\"name\":\"b\"}]}}\"#;\nlet v: Value = serde_json::from_str(raw)?;\n\n\u002F\u002F 用链式索引取值，类型不符返回 None 而不是 panic\nif let Some(name) = v[\"data\"][\"users\"][0][\"name\"].as_str() {\n    println!(\"第一个用户：{name}\");\n}\n\n\u002F\u002F json! 宏快速构造 JSON\nlet payload = serde_json::json!({\n    \"title\": \"hello\",\n    \"count\": 3,\n    \"ok\": true,\n});\n",[52,1220,1221,1233,1237,1251,1280,1284,1291,1347,1366,1370,1374,1381,1400,1412,1424,1436],{"__ignoreMap":67},[71,1222,1223,1225,1227,1229,1231],{"class":73,"line":74},[71,1224,436],{"class":99},[71,1226,580],{"class":439},[71,1228,442],{"class":99},[71,1230,1143],{"class":439},[71,1232,687],{"class":445},[71,1234,1235],{"class":73,"line":85},[71,1236,464],{"emptyLinePlaceholder":463},[71,1238,1239,1242,1244,1246,1249],{"class":73,"line":106},[71,1240,1241],{"class":99},"let",[71,1243,786],{"class":507},[71,1245,610],{"class":99},[71,1247,1248],{"class":92}," r#\"{\"code\":0,\"data\":{\"users\":[{\"name\":\"a\"},{\"name\":\"b\"}]}}\"#",[71,1250,687],{"class":445},[71,1252,1253,1255,1257,1259,1262,1264,1266,1268,1270,1272,1274,1276,1278],{"class":73,"line":121},[71,1254,1241],{"class":99},[71,1256,801],{"class":507},[71,1258,511],{"class":99},[71,1260,1261],{"class":439}," Value",[71,1263,610],{"class":99},[71,1265,580],{"class":439},[71,1267,442],{"class":99},[71,1269,814],{"class":88},[71,1271,475],{"class":445},[71,1273,819],{"class":507},[71,1275,681],{"class":445},[71,1277,684],{"class":99},[71,1279,687],{"class":445},[71,1281,1282],{"class":73,"line":259},[71,1283,464],{"emptyLinePlaceholder":463},[71,1285,1286,1288],{"class":73,"line":273},[71,1287,998],{"class":77},[71,1289,1290],{"class":81}," 用链式索引取值，类型不符返回 None 而不是 panic\n",[71,1292,1293,1296,1299,1302,1304,1307,1309,1311,1313,1316,1319,1322,1325,1327,1330,1332,1335,1338,1340,1343,1345],{"class":73,"line":286},[71,1294,1295],{"class":99},"if",[71,1297,1298],{"class":99}," let",[71,1300,1301],{"class":439}," Some",[71,1303,475],{"class":445},[71,1305,1306],{"class":507},"name",[71,1308,681],{"class":445},[71,1310,610],{"class":99},[71,1312,801],{"class":507},[71,1314,1315],{"class":445},"[",[71,1317,1318],{"class":92},"\"data\"",[71,1320,1321],{"class":445},"][",[71,1323,1324],{"class":92},"\"users\"",[71,1326,1321],{"class":445},[71,1328,1329],{"class":88},"0",[71,1331,1321],{"class":445},[71,1333,1334],{"class":92},"\"name\"",[71,1336,1337],{"class":445},"]",[71,1339,626],{"class":99},[71,1341,1342],{"class":88},"as_str",[71,1344,574],{"class":445},[71,1346,502],{"class":445},[71,1348,1349,1351,1353,1356,1358,1360,1362,1364],{"class":73,"line":300},[71,1350,693],{"class":88},[71,1352,475],{"class":445},[71,1354,1355],{"class":92},"\"第一个用户：",[71,1357,446],{"class":445},[71,1359,1306],{"class":92},[71,1361,706],{"class":445},[71,1363,698],{"class":92},[71,1365,765],{"class":445},[71,1367,1368],{"class":73,"line":316},[71,1369,559],{"class":445},[71,1371,1372],{"class":73,"line":330},[71,1373,464],{"emptyLinePlaceholder":463},[71,1375,1376,1378],{"class":73,"line":352},[71,1377,998],{"class":77},[71,1379,1380],{"class":81}," json! 宏快速构造 JSON\n",[71,1382,1383,1385,1388,1390,1392,1394,1397],{"class":73,"line":601},[71,1384,1241],{"class":99},[71,1386,1387],{"class":507}," payload",[71,1389,610],{"class":99},[71,1391,580],{"class":439},[71,1393,442],{"class":99},[71,1395,1396],{"class":88},"json!",[71,1398,1399],{"class":445},"({\n",[71,1401,1402,1405,1407,1410],{"class":73,"line":656},[71,1403,1404],{"class":92},"    \"title\"",[71,1406,511],{"class":99},[71,1408,1409],{"class":92}," \"hello\"",[71,1411,517],{"class":445},[71,1413,1414,1417,1419,1422],{"class":73,"line":690},[71,1415,1416],{"class":92},"    \"count\"",[71,1418,511],{"class":99},[71,1420,1421],{"class":88}," 3",[71,1423,517],{"class":445},[71,1425,1426,1429,1431,1434],{"class":73,"line":719},[71,1427,1428],{"class":92},"    \"ok\"",[71,1430,511],{"class":99},[71,1432,1433],{"class":88}," true",[71,1435,517],{"class":445},[71,1437,1438],{"class":73,"line":724},[71,1439,1440],{"class":445},"});\n",[57,1442,1444],{"id":1443},"_15-其他格式","1.5 其他格式",[14,1446,1447],{},"serde 是「中间表示」层，换个后端 crate 就支持别的格式，用法完全一致：",[62,1449,1451],{"className":143,"code":1450,"language":145,"meta":67,"style":67},"toml = \"0.8\"          # TOML\nserde_yaml = \"0.9\"    # YAML\n",[52,1452,1453,1458],{"__ignoreMap":67},[71,1454,1455],{"class":73,"line":74},[71,1456,1457],{},"toml = \"0.8\"          # TOML\n",[71,1459,1460],{"class":73,"line":85},[71,1461,1462],{},"serde_yaml = \"0.9\"    # YAML\n",[62,1464,1466],{"className":427,"code":1465,"language":429,"meta":67,"style":67},"let toml_str = toml::to_string(&u).unwrap();\nlet back: User = toml::from_str(&toml_str).unwrap();\n",[52,1467,1468,1500],{"__ignoreMap":67},[71,1469,1470,1472,1475,1477,1480,1482,1484,1486,1488,1490,1492,1494,1497],{"class":73,"line":74},[71,1471,1241],{"class":99},[71,1473,1474],{"class":507}," toml_str",[71,1476,610],{"class":99},[71,1478,1479],{"class":439}," toml",[71,1481,442],{"class":99},[71,1483,670],{"class":88},[71,1485,475],{"class":445},[71,1487,675],{"class":99},[71,1489,678],{"class":507},[71,1491,681],{"class":445},[71,1493,626],{"class":99},[71,1495,1496],{"class":88},"unwrap",[71,1498,1499],{"class":445},"();\n",[71,1501,1502,1504,1507,1509,1511,1513,1515,1517,1519,1521,1523,1526,1528,1530,1532],{"class":73,"line":85},[71,1503,1241],{"class":99},[71,1505,1506],{"class":507}," back",[71,1508,511],{"class":99},[71,1510,499],{"class":439},[71,1512,610],{"class":99},[71,1514,1479],{"class":439},[71,1516,442],{"class":99},[71,1518,814],{"class":88},[71,1520,475],{"class":445},[71,1522,675],{"class":99},[71,1524,1525],{"class":507},"toml_str",[71,1527,681],{"class":445},[71,1529,626],{"class":99},[71,1531,1496],{"class":88},[71,1533,1499],{"class":445},[14,1535,1536,1539],{},[18,1537,1538],{},"为什么是这个架构？"," serde 把「数据结构 ↔ 抽象数据模型」和「数据模型 ↔ 具体格式」拆开。你写的 derive 代码对 JSON\u002FTOML\u002FYAML 全部通用，社区也只维护一套宏——这就是 serde 能一统江湖的原因。",[23,1541],{},[26,1543,1545],{"id":1544},"第-2-章-anyhow-thiserror错误处理两件套","第 2 章 anyhow \u002F thiserror：错误处理两件套",[57,1547,1549],{"id":1548},"_21-它解决什么问题","2.1 它解决什么问题",[14,1551,1552,1553,1556,1557,1560],{},"Rust 没有异常，错误通过 ",[52,1554,1555],{},"Result\u003CT, E>"," 显式传递。问题是：一个函数里可能产生很多种错误（IO 错、解析错、网络错……），",[52,1558,1559],{},"E"," 该写什么类型？",[14,1562,1563],{},"社区共识：",[1565,1566,1567,1583],"table",{},[1568,1569,1570],"thead",{},[1571,1572,1573,1577,1580],"tr",{},[1574,1575,1576],"th",{},"场景",[1574,1578,1579],{},"用什么",[1574,1581,1582],{},"原因",[1584,1585,1586,1604],"tbody",{},[1571,1587,1588,1596,1601],{},[1589,1590,1591,1592,1595],"td",{},"写",[18,1593,1594],{},"应用","（bin、服务端）",[1589,1597,1598],{},[52,1599,1600],{},"anyhow",[1589,1602,1603],{},"调用者只关心「成功还是失败 + 发生了什么」，不需要细分错误类型",[1571,1605,1606,1612,1617],{},[1589,1607,1591,1608,1611],{},[18,1609,1610],{},"库","（给别人用的 crate）",[1589,1613,1614],{},[52,1615,1616],{},"thiserror",[1589,1618,1619],{},"库的调用者可能需要匹配具体错误做不同处理，错误类型必须是结构化的枚举",[57,1621,1623],{"id":1622},"_22-anyhow应用层的错误处理","2.2 anyhow：应用层的错误处理",[62,1625,1627],{"className":143,"code":1626,"language":145,"meta":67,"style":67},"[dependencies]\nanyhow = \"1\"\n",[52,1628,1629,1633],{"__ignoreMap":67},[71,1630,1631],{"class":73,"line":74},[71,1632,152],{},[71,1634,1635],{"class":73,"line":85},[71,1636,1637],{},"anyhow = \"1\"\n",[62,1639,1641],{"className":427,"code":1640,"language":429,"meta":67,"style":67},"use anyhow::{Context, Result, bail, ensure};\nuse std::fs;\n\n\u002F\u002F 返回 anyhow::Result\u003CT>，内部是 Box\u003Cdyn Error>，任何错误都能用 ? 自动转换\nfn load_config(path: &str) -> Result\u003CString> {\n    \u002F\u002F Context 给底层错误附上业务语义。这是 anyhow 最有价值的功能：\n    \u002F\u002F 没有它你只能看到 \"No such file or directory\"，不知道是哪个文件、在干嘛\n    let content = fs::read_to_string(path)\n        .with_context(|| format!(\"读取配置文件 {path} 失败\"))?;\n    Ok(content)\n}\n\nfn check_age(age: i32) -> Result\u003C()> {\n    ensure!(age >= 0, \"年龄不能为负数: {age}\");   \u002F\u002F 条件不满足则提前返回错误\n    if age > 150 {\n        bail!(\"年龄不合理: {age}\");              \u002F\u002F 直接构造错误并返回，等价于 return Err(...)\n    }\n    Ok(())\n}\n\nfn main() -> Result\u003C()> {\n    \u002F\u002F main 也可以返回 Result，出错时自动打印错误并退出码非 0\n    let cfg = load_config(\"app.toml\")?;\n    println!(\"配置长度: {}\", cfg.len());\n\n    \u002F\u002F 完整的错误链（含每一层 context）用 {:#} 打印\n    if let Err(e) = check_age(-1) {\n        eprintln!(\"错误链: {:#}\", e);\n        eprintln!(\"根因: {:?}\", e.root_cause()); \u002F\u002F 最底层那个错误\n    }\n    Ok(())\n}\n",[52,1642,1643,1674,1687,1691,1698,1733,1740,1747,1771,1808,1819,1823,1827,1854,1890,1905,1931,1936,1942,1946,1950,1966,1973,1995,2020,2025,2033,2066,2093,2129,2134,2141],{"__ignoreMap":67},[71,1644,1645,1647,1650,1652,1654,1657,1659,1662,1664,1667,1669,1672],{"class":73,"line":74},[71,1646,436],{"class":99},[71,1648,1649],{"class":439}," anyhow",[71,1651,442],{"class":99},[71,1653,446],{"class":445},[71,1655,1656],{"class":439},"Context",[71,1658,452],{"class":445},[71,1660,1661],{"class":439}," Result",[71,1663,452],{"class":445},[71,1665,1666],{"class":472}," bail",[71,1668,452],{"class":445},[71,1670,1671],{"class":472}," ensure",[71,1673,458],{"class":445},[71,1675,1676,1678,1680,1682,1685],{"class":73,"line":85},[71,1677,436],{"class":99},[71,1679,1120],{"class":439},[71,1681,442],{"class":99},[71,1683,1684],{"class":472},"fs",[71,1686,687],{"class":445},[71,1688,1689],{"class":73,"line":106},[71,1690,464],{"emptyLinePlaceholder":463},[71,1692,1693,1695],{"class":73,"line":121},[71,1694,998],{"class":77},[71,1696,1697],{"class":81}," 返回 anyhow::Result\u003CT>，内部是 Box\u003Cdyn Error>，任何错误都能用 ? 自动转换\n",[71,1699,1700,1702,1705,1707,1710,1712,1715,1718,1720,1722,1724,1726,1728,1731],{"class":73,"line":259},[71,1701,568],{"class":99},[71,1703,1704],{"class":88}," load_config",[71,1706,475],{"class":445},[71,1708,1709],{"class":507},"path",[71,1711,511],{"class":99},[71,1713,1714],{"class":99}," &",[71,1716,1717],{"class":439},"str",[71,1719,681],{"class":445},[71,1721,577],{"class":99},[71,1723,1661],{"class":439},[71,1725,542],{"class":445},[71,1727,545],{"class":439},[71,1729,1730],{"class":445},">",[71,1732,502],{"class":445},[71,1734,1735,1737],{"class":73,"line":273},[71,1736,595],{"class":77},[71,1738,1739],{"class":81}," Context 给底层错误附上业务语义。这是 anyhow 最有价值的功能：\n",[71,1741,1742,1744],{"class":73,"line":286},[71,1743,595],{"class":77},[71,1745,1746],{"class":81}," 没有它你只能看到 \"No such file or directory\"，不知道是哪个文件、在干嘛\n",[71,1748,1749,1751,1754,1756,1759,1761,1764,1766,1768],{"class":73,"line":300},[71,1750,604],{"class":99},[71,1752,1753],{"class":507}," content",[71,1755,610],{"class":99},[71,1757,1758],{"class":439}," fs",[71,1760,442],{"class":99},[71,1762,1763],{"class":88},"read_to_string",[71,1765,475],{"class":445},[71,1767,1709],{"class":507},[71,1769,1770],{"class":445},")\n",[71,1772,1773,1776,1779,1781,1784,1787,1789,1792,1794,1796,1798,1801,1804,1806],{"class":73,"line":316},[71,1774,1775],{"class":99},"        .",[71,1777,1778],{"class":88},"with_context",[71,1780,475],{"class":445},[71,1782,1783],{"class":99},"||",[71,1785,1786],{"class":88}," format!",[71,1788,475],{"class":445},[71,1790,1791],{"class":92},"\"读取配置文件 ",[71,1793,446],{"class":445},[71,1795,1709],{"class":92},[71,1797,706],{"class":445},[71,1799,1800],{"class":92}," 失败\"",[71,1802,1803],{"class":445},"))",[71,1805,684],{"class":99},[71,1807,687],{"class":445},[71,1809,1810,1812,1814,1817],{"class":73,"line":330},[71,1811,851],{"class":439},[71,1813,475],{"class":445},[71,1815,1816],{"class":507},"content",[71,1818,1770],{"class":445},[71,1820,1821],{"class":73,"line":352},[71,1822,559],{"class":445},[71,1824,1825],{"class":73,"line":601},[71,1826,464],{"emptyLinePlaceholder":463},[71,1828,1829,1831,1834,1836,1839,1841,1844,1846,1848,1850,1852],{"class":73,"line":656},[71,1830,568],{"class":99},[71,1832,1833],{"class":88}," check_age",[71,1835,475],{"class":445},[71,1837,1838],{"class":507},"age",[71,1840,511],{"class":99},[71,1842,1843],{"class":439}," i32",[71,1845,681],{"class":445},[71,1847,577],{"class":99},[71,1849,1661],{"class":439},[71,1851,588],{"class":445},[71,1853,502],{"class":445},[71,1855,1856,1859,1861,1863,1866,1869,1871,1874,1876,1878,1880,1882,1884,1887],{"class":73,"line":690},[71,1857,1858],{"class":88},"    ensure!",[71,1860,475],{"class":445},[71,1862,1838],{"class":507},[71,1864,1865],{"class":99}," >=",[71,1867,1868],{"class":88}," 0",[71,1870,452],{"class":445},[71,1872,1873],{"class":92}," \"年龄不能为负数: ",[71,1875,446],{"class":445},[71,1877,1838],{"class":92},[71,1879,706],{"class":445},[71,1881,698],{"class":92},[71,1883,711],{"class":445},[71,1885,1886],{"class":77},"   \u002F\u002F",[71,1888,1889],{"class":81}," 条件不满足则提前返回错误\n",[71,1891,1892,1895,1897,1900,1903],{"class":73,"line":719},[71,1893,1894],{"class":99},"    if",[71,1896,635],{"class":507},[71,1898,1899],{"class":99}," >",[71,1901,1902],{"class":88}," 150",[71,1904,502],{"class":445},[71,1906,1907,1910,1912,1915,1917,1919,1921,1923,1925,1928],{"class":73,"line":724},[71,1908,1909],{"class":88},"        bail!",[71,1911,475],{"class":445},[71,1913,1914],{"class":92},"\"年龄不合理: ",[71,1916,446],{"class":445},[71,1918,1838],{"class":92},[71,1920,706],{"class":445},[71,1922,698],{"class":92},[71,1924,711],{"class":445},[71,1926,1927],{"class":77},"              \u002F\u002F",[71,1929,1930],{"class":81}," 直接构造错误并返回，等价于 return Err(...)\n",[71,1932,1933],{"class":73,"line":732},[71,1934,1935],{"class":445},"    }\n",[71,1937,1938,1940],{"class":73,"line":768},[71,1939,851],{"class":439},[71,1941,854],{"class":445},[71,1943,1944],{"class":73,"line":773},[71,1945,559],{"class":445},[71,1947,1948],{"class":73,"line":781},[71,1949,464],{"emptyLinePlaceholder":463},[71,1951,1952,1954,1956,1958,1960,1962,1964],{"class":73,"line":796},[71,1953,568],{"class":99},[71,1955,571],{"class":88},[71,1957,574],{"class":445},[71,1959,577],{"class":99},[71,1961,1661],{"class":439},[71,1963,588],{"class":445},[71,1965,502],{"class":445},[71,1967,1968,1970],{"class":73,"line":828},[71,1969,595],{"class":77},[71,1971,1972],{"class":81}," main 也可以返回 Result，出错时自动打印错误并退出码非 0\n",[71,1974,1975,1977,1980,1982,1984,1986,1989,1991,1993],{"class":73,"line":848},[71,1976,604],{"class":99},[71,1978,1979],{"class":507}," cfg",[71,1981,610],{"class":99},[71,1983,1704],{"class":88},[71,1985,475],{"class":445},[71,1987,1988],{"class":92},"\"app.toml\"",[71,1990,681],{"class":445},[71,1992,684],{"class":99},[71,1994,687],{"class":445},[71,1996,1997,1999,2001,2004,2006,2008,2010,2012,2014,2017],{"class":73,"line":857},[71,1998,693],{"class":88},[71,2000,475],{"class":445},[71,2002,2003],{"class":92},"\"配置长度: ",[71,2005,741],{"class":445},[71,2007,698],{"class":92},[71,2009,452],{"class":445},[71,2011,1979],{"class":507},[71,2013,626],{"class":99},[71,2015,2016],{"class":88},"len",[71,2018,2019],{"class":445},"());\n",[71,2021,2023],{"class":73,"line":2022},25,[71,2024,464],{"emptyLinePlaceholder":463},[71,2026,2028,2030],{"class":73,"line":2027},26,[71,2029,595],{"class":77},[71,2031,2032],{"class":81}," 完整的错误链（含每一层 context）用 {:#} 打印\n",[71,2034,2036,2038,2040,2043,2045,2048,2050,2052,2054,2056,2059,2062,2064],{"class":73,"line":2035},27,[71,2037,1894],{"class":99},[71,2039,1298],{"class":99},[71,2041,2042],{"class":439}," Err",[71,2044,475],{"class":445},[71,2046,2047],{"class":507},"e",[71,2049,681],{"class":445},[71,2051,610],{"class":99},[71,2053,1833],{"class":88},[71,2055,475],{"class":445},[71,2057,2058],{"class":99},"-",[71,2060,2061],{"class":88},"1",[71,2063,681],{"class":445},[71,2065,502],{"class":445},[71,2067,2069,2072,2074,2077,2079,2082,2084,2086,2088,2091],{"class":73,"line":2068},28,[71,2070,2071],{"class":88},"        eprintln!",[71,2073,475],{"class":445},[71,2075,2076],{"class":92},"\"错误链: ",[71,2078,446],{"class":445},[71,2080,2081],{"class":92},":#",[71,2083,706],{"class":445},[71,2085,698],{"class":92},[71,2087,452],{"class":445},[71,2089,2090],{"class":507}," e",[71,2092,765],{"class":445},[71,2094,2096,2098,2100,2103,2105,2108,2110,2112,2114,2116,2118,2121,2124,2126],{"class":73,"line":2095},29,[71,2097,2071],{"class":88},[71,2099,475],{"class":445},[71,2101,2102],{"class":92},"\"根因: ",[71,2104,446],{"class":445},[71,2106,2107],{"class":92},":?",[71,2109,706],{"class":445},[71,2111,698],{"class":92},[71,2113,452],{"class":445},[71,2115,2090],{"class":507},[71,2117,626],{"class":99},[71,2119,2120],{"class":88},"root_cause",[71,2122,2123],{"class":445},"());",[71,2125,551],{"class":77},[71,2127,2128],{"class":81}," 最底层那个错误\n",[71,2130,2132],{"class":73,"line":2131},30,[71,2133,1935],{"class":445},[71,2135,2137,2139],{"class":73,"line":2136},31,[71,2138,851],{"class":439},[71,2140,854],{"class":445},[71,2142,2144],{"class":73,"line":2143},32,[71,2145,559],{"class":445},[14,2147,2148,2153,2154,2157],{},[18,2149,2150,2151,1193],{},"为什么要 ",[52,2152,1656],{}," 底层的 ",[52,2155,2156],{},"io::Error"," 只说「文件不存在」，但排查线上问题你需要知道「是在加载哪个配置时失败的」。Context 是在错误向上传播的路上不断追加业务上下文，形成错误链——这是用很低成本换来极好排障体验的做法。",[57,2159,2161],{"id":2160},"_23-thiserror给库定义错误类型","2.3 thiserror：给库定义错误类型",[62,2163,2165],{"className":143,"code":2164,"language":145,"meta":67,"style":67},"[dependencies]\nthiserror = \"2\"\n",[52,2166,2167,2171],{"__ignoreMap":67},[71,2168,2169],{"class":73,"line":74},[71,2170,152],{},[71,2172,2173],{"class":73,"line":85},[71,2174,2175],{},"thiserror = \"2\"\n",[62,2177,2179],{"className":427,"code":2178,"language":429,"meta":67,"style":67},"use thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum StoreError {\n    #[error(\"记录 {0} 不存在\")]                 \u002F\u002F 错误信息模板，{0} 引用元组字段\n    NotFound(u64),\n\n    #[error(\"权限不足，需要 {need} 级，当前 {current} 级\")] \u002F\u002F 命名字段也可以\n    PermissionDenied { need: u8, current: u8 },\n\n    #[error(\"数据库错误\")]                       \u002F\u002F 展示给上层的文案\n    #[from]                                     \u002F\u002F 自动生成 From\u003Csqlx::Error>，? 可自动转换\n    Db(#[source] sqlx::Error),                  \u002F\u002F #[source] 保留底层错误供溯源\n}\n\nfn find_user(id: u64) -> Result\u003CString, StoreError> {\n    if id == 0 { return Err(StoreError::NotFound(id)); }\n    Ok(\"小明\".into())\n}\n\n\u002F\u002F 调用方可以精确匹配\nfn handle() {\n    match find_user(0) {\n        Err(StoreError::NotFound(id)) => println!(\"404: {id}\"),\n        Err(StoreError::Db(e)) => println!(\"数据库挂了: {e}\"),\n        _ => {}\n        Ok(name) => println!(\"{name}\"),\n    }\n}\n",[52,2180,2181,2195,2199,2215,2228,2257,2270,2274,2312,2338,2342,2361,2376,2405,2409,2413,2447,2485,2501,2505,2509,2516,2527,2542,2582,2620,2630,2659,2663],{"__ignoreMap":67},[71,2182,2183,2185,2188,2190,2193],{"class":73,"line":74},[71,2184,436],{"class":99},[71,2186,2187],{"class":439}," thiserror",[71,2189,442],{"class":99},[71,2191,2192],{"class":439},"Error",[71,2194,687],{"class":445},[71,2196,2197],{"class":73,"line":85},[71,2198,464],{"emptyLinePlaceholder":463},[71,2200,2201,2203,2205,2207,2209,2211,2213],{"class":73,"line":106},[71,2202,469],{"class":445},[71,2204,176],{"class":472},[71,2206,475],{"class":445},[71,2208,2192],{"class":439},[71,2210,452],{"class":445},[71,2212,488],{"class":439},[71,2214,491],{"class":445},[71,2216,2217,2220,2223,2226],{"class":73,"line":121},[71,2218,2219],{"class":99},"pub",[71,2221,2222],{"class":99}," enum",[71,2224,2225],{"class":439}," StoreError",[71,2227,502],{"class":445},[71,2229,2230,2232,2235,2237,2240,2242,2244,2246,2249,2251,2254],{"class":73,"line":259},[71,2231,898],{"class":445},[71,2233,2234],{"class":472},"error",[71,2236,475],{"class":445},[71,2238,2239],{"class":92},"\"记录 ",[71,2241,446],{"class":445},[71,2243,1329],{"class":92},[71,2245,706],{"class":445},[71,2247,2248],{"class":92}," 不存在\"",[71,2250,915],{"class":445},[71,2252,2253],{"class":77},"                 \u002F\u002F",[71,2255,2256],{"class":81}," 错误信息模板，{0} 引用元组字段\n",[71,2258,2259,2262,2264,2267],{"class":73,"line":273},[71,2260,2261],{"class":88},"    NotFound",[71,2263,475],{"class":445},[71,2265,2266],{"class":439},"u64",[71,2268,2269],{"class":445},"),\n",[71,2271,2272],{"class":73,"line":286},[71,2273,464],{"emptyLinePlaceholder":463},[71,2275,2276,2278,2280,2282,2285,2287,2290,2292,2295,2297,2300,2302,2305,2307,2309],{"class":73,"line":300},[71,2277,898],{"class":445},[71,2279,2234],{"class":472},[71,2281,475],{"class":445},[71,2283,2284],{"class":92},"\"权限不足，需要 ",[71,2286,446],{"class":445},[71,2288,2289],{"class":92},"need",[71,2291,706],{"class":445},[71,2293,2294],{"class":92}," 级，当前 ",[71,2296,446],{"class":445},[71,2298,2299],{"class":92},"current",[71,2301,706],{"class":445},[71,2303,2304],{"class":92}," 级\"",[71,2306,915],{"class":445},[71,2308,551],{"class":77},[71,2310,2311],{"class":81}," 命名字段也可以\n",[71,2313,2314,2317,2319,2322,2324,2326,2328,2331,2333,2335],{"class":73,"line":316},[71,2315,2316],{"class":439},"    PermissionDenied",[71,2318,615],{"class":445},[71,2320,2321],{"class":507}," need",[71,2323,511],{"class":99},[71,2325,527],{"class":439},[71,2327,452],{"class":445},[71,2329,2330],{"class":507}," current",[71,2332,511],{"class":99},[71,2334,527],{"class":439},[71,2336,2337],{"class":445}," },\n",[71,2339,2340],{"class":73,"line":330},[71,2341,464],{"emptyLinePlaceholder":463},[71,2343,2344,2346,2348,2350,2353,2355,2358],{"class":73,"line":352},[71,2345,898],{"class":445},[71,2347,2234],{"class":472},[71,2349,475],{"class":445},[71,2351,2352],{"class":92},"\"数据库错误\"",[71,2354,915],{"class":445},[71,2356,2357],{"class":77},"                       \u002F\u002F",[71,2359,2360],{"class":81}," 展示给上层的文案\n",[71,2362,2363,2365,2368,2370,2373],{"class":73,"line":601},[71,2364,898],{"class":445},[71,2366,2367],{"class":472},"from",[71,2369,1337],{"class":445},[71,2371,2372],{"class":77},"                                     \u002F\u002F",[71,2374,2375],{"class":81}," 自动生成 From\u003Csqlx::Error>，? 可自动转换\n",[71,2377,2378,2381,2384,2387,2389,2392,2394,2396,2399,2402],{"class":73,"line":656},[71,2379,2380],{"class":88},"    Db",[71,2382,2383],{"class":445},"(#[",[71,2385,2386],{"class":472},"source",[71,2388,1337],{"class":445},[71,2390,2391],{"class":439}," sqlx",[71,2393,442],{"class":99},[71,2395,2192],{"class":439},[71,2397,2398],{"class":445},"),",[71,2400,2401],{"class":77},"                  \u002F\u002F",[71,2403,2404],{"class":81}," #[source] 保留底层错误供溯源\n",[71,2406,2407],{"class":73,"line":690},[71,2408,559],{"class":445},[71,2410,2411],{"class":73,"line":719},[71,2412,464],{"emptyLinePlaceholder":463},[71,2414,2415,2417,2420,2422,2425,2427,2429,2431,2433,2435,2437,2439,2441,2443,2445],{"class":73,"line":724},[71,2416,568],{"class":99},[71,2418,2419],{"class":88}," find_user",[71,2421,475],{"class":445},[71,2423,2424],{"class":507},"id",[71,2426,511],{"class":99},[71,2428,931],{"class":439},[71,2430,681],{"class":445},[71,2432,577],{"class":99},[71,2434,1661],{"class":439},[71,2436,542],{"class":445},[71,2438,545],{"class":439},[71,2440,452],{"class":445},[71,2442,2225],{"class":439},[71,2444,1730],{"class":445},[71,2446,502],{"class":445},[71,2448,2449,2451,2454,2457,2459,2461,2464,2466,2468,2471,2473,2476,2478,2480,2483],{"class":73,"line":732},[71,2450,1894],{"class":99},[71,2452,2453],{"class":507}," id",[71,2455,2456],{"class":99}," ==",[71,2458,1868],{"class":88},[71,2460,615],{"class":445},[71,2462,2463],{"class":99}," return",[71,2465,2042],{"class":439},[71,2467,475],{"class":445},[71,2469,2470],{"class":439},"StoreError",[71,2472,442],{"class":99},[71,2474,2475],{"class":88},"NotFound",[71,2477,475],{"class":445},[71,2479,2424],{"class":507},[71,2481,2482],{"class":445},"));",[71,2484,1180],{"class":445},[71,2486,2487,2489,2491,2494,2496,2498],{"class":73,"line":768},[71,2488,851],{"class":439},[71,2490,475],{"class":445},[71,2492,2493],{"class":92},"\"小明\"",[71,2495,626],{"class":99},[71,2497,629],{"class":88},[71,2499,2500],{"class":445},"())\n",[71,2502,2503],{"class":73,"line":773},[71,2504,559],{"class":445},[71,2506,2507],{"class":73,"line":781},[71,2508,464],{"emptyLinePlaceholder":463},[71,2510,2511,2513],{"class":73,"line":796},[71,2512,998],{"class":77},[71,2514,2515],{"class":81}," 调用方可以精确匹配\n",[71,2517,2518,2520,2523,2525],{"class":73,"line":828},[71,2519,568],{"class":99},[71,2521,2522],{"class":88}," handle",[71,2524,574],{"class":445},[71,2526,502],{"class":445},[71,2528,2529,2532,2534,2536,2538,2540],{"class":73,"line":848},[71,2530,2531],{"class":99},"    match",[71,2533,2419],{"class":88},[71,2535,475],{"class":445},[71,2537,1329],{"class":88},[71,2539,681],{"class":445},[71,2541,502],{"class":445},[71,2543,2544,2547,2549,2551,2553,2555,2557,2559,2561,2564,2567,2569,2572,2574,2576,2578,2580],{"class":73,"line":857},[71,2545,2546],{"class":439},"        Err",[71,2548,475],{"class":445},[71,2550,2470],{"class":439},[71,2552,442],{"class":99},[71,2554,2475],{"class":88},[71,2556,475],{"class":445},[71,2558,2424],{"class":507},[71,2560,1803],{"class":445},[71,2562,2563],{"class":99}," =>",[71,2565,2566],{"class":88}," println!",[71,2568,475],{"class":445},[71,2570,2571],{"class":92},"\"404: ",[71,2573,446],{"class":445},[71,2575,2424],{"class":92},[71,2577,706],{"class":445},[71,2579,698],{"class":92},[71,2581,2269],{"class":445},[71,2583,2584,2586,2588,2590,2592,2595,2597,2599,2601,2603,2605,2607,2610,2612,2614,2616,2618],{"class":73,"line":2022},[71,2585,2546],{"class":439},[71,2587,475],{"class":445},[71,2589,2470],{"class":439},[71,2591,442],{"class":99},[71,2593,2594],{"class":88},"Db",[71,2596,475],{"class":445},[71,2598,2047],{"class":507},[71,2600,1803],{"class":445},[71,2602,2563],{"class":99},[71,2604,2566],{"class":88},[71,2606,475],{"class":445},[71,2608,2609],{"class":92},"\"数据库挂了: ",[71,2611,446],{"class":445},[71,2613,2047],{"class":92},[71,2615,706],{"class":445},[71,2617,698],{"class":92},[71,2619,2269],{"class":445},[71,2621,2622,2625,2627],{"class":73,"line":2027},[71,2623,2624],{"class":507},"        _",[71,2626,2563],{"class":99},[71,2628,2629],{"class":445}," {}\n",[71,2631,2632,2635,2637,2639,2641,2643,2645,2647,2649,2651,2653,2655,2657],{"class":73,"line":2035},[71,2633,2634],{"class":439},"        Ok",[71,2636,475],{"class":445},[71,2638,1306],{"class":507},[71,2640,681],{"class":445},[71,2642,2563],{"class":99},[71,2644,2566],{"class":88},[71,2646,475],{"class":445},[71,2648,698],{"class":92},[71,2650,446],{"class":445},[71,2652,1306],{"class":92},[71,2654,706],{"class":445},[71,2656,698],{"class":92},[71,2658,2269],{"class":445},[71,2660,2661],{"class":73,"line":2068},[71,2662,1935],{"class":445},[71,2664,2665],{"class":73,"line":2095},[71,2666,559],{"class":445},[14,2668,2669,2672,2673,2676,2677,2680,2681,2683,2684,2687,2688,2687,2691,2694],{},[18,2670,2671],{},"为什么库不用 anyhow？"," 如果你的库返回 ",[52,2674,2675],{},"anyhow::Error","，调用方就没法 ",[52,2678,2679],{},"match"," 具体错误种类（anyhow 内部是类型擦除的），只能靠字符串匹配——这对库消费者来说是不可接受的。",[52,2682,1616],{}," 只是帮你少写 ",[52,2685,2686],{},"impl Display"," \u002F ",[52,2689,2690],{},"impl Error",[52,2692,2693],{},"impl From"," 的样板代码，产出的是完全标准、类型安全的错误枚举。",[11,2696,2697],{},[14,2698,2699,2700,2702,2703,2705],{},"补充：应用层也可以用「",[52,2701,1616],{}," 定义领域错误 + ",[52,2704,1600],{}," 做顶层汇总」的混合模式，二者不冲突。",[23,2707],{},[26,2709,2711],{"id":2710},"第-3-章-tokio异步运行时","第 3 章 tokio：异步运行时",[57,2713,2715],{"id":2714},"_31-它解决什么问题","3.1 它解决什么问题",[14,2717,2718,2719,2722,2723,2726,2727,2730,2731,2734],{},"写网络服务时，一个线程阻塞等 IO 是巨大浪费。Rust 的 ",[52,2720,2721],{},"async\u002Fawait"," 只是",[18,2724,2725],{},"语法","，",[52,2728,2729],{},"async fn"," 本身不会运行——必须有一个",[18,2732,2733],{},"运行时","来轮询、调度这些 future。tokio 是事实标准的异步运行时（带工作窃取调度器、定时器、异步 IO、异步同步原语）。",[14,2736,2737,2740],{},[18,2738,2739],{},"为什么 Rust 异步要显式选运行时？"," 因为嵌入式、浏览器 WASM、服务端对调度和 IO 的需求完全不同，标准库不替你决定。代价是入门多一步，收益是各场景都能用最优实现。",[62,2742,2744],{"className":143,"code":2743,"language":145,"meta":67,"style":67},"[dependencies]\ntokio = { version = \"1\", features = [\"full\"] } # 学习中直接 full，生产再裁剪\n",[52,2745,2746,2750],{"__ignoreMap":67},[71,2747,2748],{"class":73,"line":74},[71,2749,152],{},[71,2751,2752],{"class":73,"line":85},[71,2753,2754],{},"tokio = { version = \"1\", features = [\"full\"] } # 学习中直接 full，生产再裁剪\n",[57,2756,2758],{"id":2757},"_32-入口与-spawn","3.2 入口与 spawn",[62,2760,2762],{"className":427,"code":2761,"language":429,"meta":67,"style":67},"\u002F\u002F #[tokio::main] 宏把 main 变成运行时入口，等价于手动 Builder::new_multi_thread()...block_on\n#[tokio::main]\nasync fn main() {\n    let handle = tokio::spawn(async {\n        \u002F\u002F spawn 创建一个新的异步任务，由运行时并发调度\n        \u002F\u002F 类似线程，但成本极低：可以轻松起十万级任务\n        tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n        42\n    });\n\n    \u002F\u002F JoinHandle 类似线程的 join，await 拿到任务返回值\n    let answer = handle.await.unwrap();\n    println!(\"{answer}\");\n}\n",[52,2763,2764,2771,2786,2800,2821,2829,2836,2881,2886,2891,2895,2902,2920,2939],{"__ignoreMap":67},[71,2765,2766,2768],{"class":73,"line":74},[71,2767,998],{"class":77},[71,2769,2770],{"class":81}," #[tokio::main] 宏把 main 变成运行时入口，等价于手动 Builder::new_multi_thread()...block_on\n",[71,2772,2773,2775,2778,2780,2783],{"class":73,"line":85},[71,2774,469],{"class":445},[71,2776,2777],{"class":472},"tokio",[71,2779,442],{"class":99},[71,2781,2782],{"class":472},"main",[71,2784,2785],{"class":445},"]\n",[71,2787,2788,2791,2794,2796,2798],{"class":73,"line":106},[71,2789,2790],{"class":99},"async",[71,2792,2793],{"class":99}," fn",[71,2795,571],{"class":88},[71,2797,574],{"class":445},[71,2799,502],{"class":445},[71,2801,2802,2804,2806,2808,2810,2812,2815,2817,2819],{"class":73,"line":121},[71,2803,604],{"class":99},[71,2805,2522],{"class":507},[71,2807,610],{"class":99},[71,2809,113],{"class":439},[71,2811,442],{"class":99},[71,2813,2814],{"class":88},"spawn",[71,2816,475],{"class":445},[71,2818,2790],{"class":99},[71,2820,502],{"class":445},[71,2822,2823,2826],{"class":73,"line":259},[71,2824,2825],{"class":77},"        \u002F\u002F",[71,2827,2828],{"class":81}," spawn 创建一个新的异步任务，由运行时并发调度\n",[71,2830,2831,2833],{"class":73,"line":273},[71,2832,2825],{"class":77},[71,2834,2835],{"class":81}," 类似线程，但成本极低：可以轻松起十万级任务\n",[71,2837,2838,2841,2843,2846,2848,2851,2853,2856,2858,2860,2862,2865,2867,2870,2872,2874,2876,2879],{"class":73,"line":286},[71,2839,2840],{"class":439},"        tokio",[71,2842,442],{"class":99},[71,2844,2845],{"class":439},"time",[71,2847,442],{"class":99},[71,2849,2850],{"class":88},"sleep",[71,2852,475],{"class":445},[71,2854,2855],{"class":439},"std",[71,2857,442],{"class":99},[71,2859,2845],{"class":439},[71,2861,442],{"class":99},[71,2863,2864],{"class":439},"Duration",[71,2866,442],{"class":99},[71,2868,2869],{"class":88},"from_secs",[71,2871,475],{"class":445},[71,2873,2061],{"class":88},[71,2875,1803],{"class":445},[71,2877,2878],{"class":99},".await",[71,2880,687],{"class":445},[71,2882,2883],{"class":73,"line":300},[71,2884,2885],{"class":88},"        42\n",[71,2887,2888],{"class":73,"line":316},[71,2889,2890],{"class":445},"    });\n",[71,2892,2893],{"class":73,"line":330},[71,2894,464],{"emptyLinePlaceholder":463},[71,2896,2897,2899],{"class":73,"line":352},[71,2898,595],{"class":77},[71,2900,2901],{"class":81}," JoinHandle 类似线程的 join，await 拿到任务返回值\n",[71,2903,2904,2906,2909,2911,2913,2916,2918],{"class":73,"line":601},[71,2905,604],{"class":99},[71,2907,2908],{"class":507}," answer",[71,2910,610],{"class":99},[71,2912,2522],{"class":507},[71,2914,2915],{"class":99},".await.",[71,2917,1496],{"class":88},[71,2919,1499],{"class":445},[71,2921,2922,2924,2926,2928,2930,2933,2935,2937],{"class":73,"line":656},[71,2923,693],{"class":88},[71,2925,475],{"class":445},[71,2927,698],{"class":92},[71,2929,446],{"class":445},[71,2931,2932],{"class":92},"answer",[71,2934,706],{"class":445},[71,2936,698],{"class":92},[71,2938,765],{"class":445},[71,2940,2941],{"class":73,"line":690},[71,2942,559],{"class":445},[57,2944,2946],{"id":2945},"_33-并发等待join-try_join-select","3.3 并发等待：join! \u002F try_join! \u002F select!",[62,2948,2950],{"className":427,"code":2949,"language":429,"meta":67,"style":67},"use tokio::time::{sleep, Duration};\n\nasync fn fetch_user() -> Result\u003CString, String> { sleep(Duration::from_millis(300)).await; Ok(\"user\".into()) }\nasync fn fetch_orders() -> Result\u003CVec\u003CString>, String> { sleep(Duration::from_millis(500)).await; Ok(vec![\"o1\".into()]) }\n\n#[tokio::main]\nasync fn main() {\n    \u002F\u002F join!: 并发执行，全部完成后一起拿结果。总耗时取最长的那个（500ms 而非 800ms）\n    let (u, o) = tokio::join!(fetch_user(), fetch_orders());\n\n    \u002F\u002F try_join!: 任何一个出错立即返回 Err，其余取消。适合「缺一不可」的聚合请求\n    let r: Result\u003C(String, Vec\u003CString>), String> = tokio::try_join!(fetch_user(), fetch_orders());\n\n    \u002F\u002F select!: 谁先完成用谁，其余被 drop。适合超时、竞速备份请求\n    tokio::select! {\n        v = fetch_user() => println!(\"user 先到: {v:?}\"),\n        _ = sleep(Duration::from_secs(10)) => println!(\"超时了\"),\n    }\n    let _ = (u, o, r);\n}\n",[52,2951,2952,2975,2979,3047,3121,3125,3137,3149,3156,3192,3196,3203,3253,3257,3264,3276,3306,3340,3344,3367],{"__ignoreMap":67},[71,2953,2954,2956,2958,2960,2962,2964,2966,2968,2970,2973],{"class":73,"line":74},[71,2955,436],{"class":99},[71,2957,113],{"class":439},[71,2959,442],{"class":99},[71,2961,2845],{"class":439},[71,2963,442],{"class":99},[71,2965,446],{"class":445},[71,2967,2850],{"class":472},[71,2969,452],{"class":445},[71,2971,2972],{"class":439}," Duration",[71,2974,458],{"class":445},[71,2976,2977],{"class":73,"line":85},[71,2978,464],{"emptyLinePlaceholder":463},[71,2980,2981,2983,2985,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3009,3011,3013,3015,3018,3020,3023,3025,3027,3030,3033,3035,3038,3040,3042,3045],{"class":73,"line":106},[71,2982,2790],{"class":99},[71,2984,2793],{"class":99},[71,2986,2987],{"class":88}," fetch_user",[71,2989,574],{"class":445},[71,2991,577],{"class":99},[71,2993,1661],{"class":439},[71,2995,542],{"class":445},[71,2997,545],{"class":439},[71,2999,452],{"class":445},[71,3001,514],{"class":439},[71,3003,1730],{"class":445},[71,3005,615],{"class":445},[71,3007,3008],{"class":88}," sleep",[71,3010,475],{"class":445},[71,3012,2864],{"class":439},[71,3014,442],{"class":99},[71,3016,3017],{"class":88},"from_millis",[71,3019,475],{"class":445},[71,3021,3022],{"class":88},"300",[71,3024,1803],{"class":445},[71,3026,2878],{"class":99},[71,3028,3029],{"class":445},";",[71,3031,3032],{"class":439}," Ok",[71,3034,475],{"class":445},[71,3036,3037],{"class":92},"\"user\"",[71,3039,626],{"class":99},[71,3041,629],{"class":88},[71,3043,3044],{"class":445},"())",[71,3046,1180],{"class":445},[71,3048,3049,3051,3053,3056,3058,3060,3062,3064,3067,3069,3071,3073,3075,3077,3079,3081,3083,3085,3087,3089,3091,3094,3096,3098,3100,3102,3104,3107,3109,3112,3114,3116,3119],{"class":73,"line":121},[71,3050,2790],{"class":99},[71,3052,2793],{"class":99},[71,3054,3055],{"class":88}," fetch_orders",[71,3057,574],{"class":445},[71,3059,577],{"class":99},[71,3061,1661],{"class":439},[71,3063,542],{"class":445},[71,3065,3066],{"class":439},"Vec",[71,3068,542],{"class":445},[71,3070,545],{"class":439},[71,3072,548],{"class":445},[71,3074,514],{"class":439},[71,3076,1730],{"class":445},[71,3078,615],{"class":445},[71,3080,3008],{"class":88},[71,3082,475],{"class":445},[71,3084,2864],{"class":439},[71,3086,442],{"class":99},[71,3088,3017],{"class":88},[71,3090,475],{"class":445},[71,3092,3093],{"class":88},"500",[71,3095,1803],{"class":445},[71,3097,2878],{"class":99},[71,3099,3029],{"class":445},[71,3101,3032],{"class":439},[71,3103,475],{"class":445},[71,3105,3106],{"class":88},"vec!",[71,3108,1315],{"class":445},[71,3110,3111],{"class":92},"\"o1\"",[71,3113,626],{"class":99},[71,3115,629],{"class":88},[71,3117,3118],{"class":445},"()])",[71,3120,1180],{"class":445},[71,3122,3123],{"class":73,"line":259},[71,3124,464],{"emptyLinePlaceholder":463},[71,3126,3127,3129,3131,3133,3135],{"class":73,"line":273},[71,3128,469],{"class":445},[71,3130,2777],{"class":472},[71,3132,442],{"class":99},[71,3134,2782],{"class":472},[71,3136,2785],{"class":445},[71,3138,3139,3141,3143,3145,3147],{"class":73,"line":286},[71,3140,2790],{"class":99},[71,3142,2793],{"class":99},[71,3144,571],{"class":88},[71,3146,574],{"class":445},[71,3148,502],{"class":445},[71,3150,3151,3153],{"class":73,"line":300},[71,3152,595],{"class":77},[71,3154,3155],{"class":81}," join!: 并发执行，全部完成后一起拿结果。总耗时取最长的那个（500ms 而非 800ms）\n",[71,3157,3158,3160,3163,3165,3167,3170,3172,3174,3176,3178,3181,3183,3186,3188,3190],{"class":73,"line":316},[71,3159,604],{"class":99},[71,3161,3162],{"class":445}," (",[71,3164,678],{"class":507},[71,3166,452],{"class":445},[71,3168,3169],{"class":507}," o",[71,3171,681],{"class":445},[71,3173,610],{"class":99},[71,3175,113],{"class":439},[71,3177,442],{"class":99},[71,3179,3180],{"class":88},"join!",[71,3182,475],{"class":445},[71,3184,3185],{"class":88},"fetch_user",[71,3187,632],{"class":445},[71,3189,3055],{"class":88},[71,3191,2019],{"class":445},[71,3193,3194],{"class":73,"line":330},[71,3195,464],{"emptyLinePlaceholder":463},[71,3197,3198,3200],{"class":73,"line":352},[71,3199,595],{"class":77},[71,3201,3202],{"class":81}," try_join!: 任何一个出错立即返回 Err，其余取消。适合「缺一不可」的聚合请求\n",[71,3204,3205,3207,3210,3212,3214,3217,3219,3221,3223,3225,3227,3230,3232,3234,3236,3238,3240,3243,3245,3247,3249,3251],{"class":73,"line":601},[71,3206,604],{"class":99},[71,3208,3209],{"class":507}," r",[71,3211,511],{"class":99},[71,3213,1661],{"class":439},[71,3215,3216],{"class":445},"\u003C(",[71,3218,545],{"class":439},[71,3220,452],{"class":445},[71,3222,966],{"class":439},[71,3224,542],{"class":445},[71,3226,545],{"class":439},[71,3228,3229],{"class":445},">),",[71,3231,514],{"class":439},[71,3233,1730],{"class":445},[71,3235,610],{"class":99},[71,3237,113],{"class":439},[71,3239,442],{"class":99},[71,3241,3242],{"class":88},"try_join!",[71,3244,475],{"class":445},[71,3246,3185],{"class":88},[71,3248,632],{"class":445},[71,3250,3055],{"class":88},[71,3252,2019],{"class":445},[71,3254,3255],{"class":73,"line":656},[71,3256,464],{"emptyLinePlaceholder":463},[71,3258,3259,3261],{"class":73,"line":690},[71,3260,595],{"class":77},[71,3262,3263],{"class":81}," select!: 谁先完成用谁，其余被 drop。适合超时、竞速备份请求\n",[71,3265,3266,3269,3271,3274],{"class":73,"line":719},[71,3267,3268],{"class":439},"    tokio",[71,3270,442],{"class":99},[71,3272,3273],{"class":88},"select!",[71,3275,502],{"class":445},[71,3277,3278,3281,3283,3285,3287,3289,3291,3293,3296,3298,3300,3302,3304],{"class":73,"line":724},[71,3279,3280],{"class":507},"        v",[71,3282,610],{"class":99},[71,3284,2987],{"class":88},[71,3286,574],{"class":445},[71,3288,2563],{"class":99},[71,3290,2566],{"class":88},[71,3292,475],{"class":445},[71,3294,3295],{"class":92},"\"user 先到: ",[71,3297,446],{"class":445},[71,3299,839],{"class":92},[71,3301,706],{"class":445},[71,3303,698],{"class":92},[71,3305,2269],{"class":445},[71,3307,3308,3310,3312,3314,3316,3318,3320,3322,3324,3327,3329,3331,3333,3335,3338],{"class":73,"line":732},[71,3309,2624],{"class":507},[71,3311,610],{"class":99},[71,3313,3008],{"class":88},[71,3315,475],{"class":445},[71,3317,2864],{"class":439},[71,3319,442],{"class":99},[71,3321,2869],{"class":88},[71,3323,475],{"class":445},[71,3325,3326],{"class":88},"10",[71,3328,1803],{"class":445},[71,3330,2563],{"class":99},[71,3332,2566],{"class":88},[71,3334,475],{"class":445},[71,3336,3337],{"class":92},"\"超时了\"",[71,3339,2269],{"class":445},[71,3341,3342],{"class":73,"line":768},[71,3343,1935],{"class":445},[71,3345,3346,3348,3351,3353,3355,3357,3359,3361,3363,3365],{"class":73,"line":773},[71,3347,604],{"class":99},[71,3349,3350],{"class":507}," _",[71,3352,610],{"class":99},[71,3354,3162],{"class":445},[71,3356,678],{"class":507},[71,3358,452],{"class":445},[71,3360,3169],{"class":507},[71,3362,452],{"class":445},[71,3364,3209],{"class":507},[71,3366,765],{"class":445},[71,3368,3369],{"class":73,"line":781},[71,3370,559],{"class":445},[14,3372,3373,3380,3381,3383,3384,375],{},[18,3374,2150,3375,3377,3378,1193],{},[52,3376,3180],{}," 而不是依次 ",[52,3379,2878],{}," 依次 await 是串行：等 user 回来再发 orders 请求，总延迟翻倍。",[52,3382,3180],{}," 让多个 IO 同时在飞，这是异步最大的收益场景之一——",[18,3385,3386],{},"IO 聚合并发",[57,3388,3390],{"id":3389},"_34-超时与取消","3.4 超时与取消",[62,3392,3394],{"className":427,"code":3393,"language":429,"meta":67,"style":67},"use tokio::time::{timeout, Duration};\n\nasync fn slow() -> &'static str { tokio::time::sleep(Duration::from_secs(5)).await; \"done\" }\n\n#[tokio::main]\nasync fn main() {\n    \u002F\u002F timeout 是 select! 的常见封装：到点未完成就返回 Err\n    match timeout(Duration::from_secs(1), slow()).await {\n        Ok(v) => println!(\"{v}\"),\n        Err(_) => println!(\"超时取消\"), \u002F\u002F 超时时 future 被 drop，任务自然取消\n    }\n}\n",[52,3395,3396,3419,3423,3483,3487,3499,3511,3518,3547,3576,3603,3607],{"__ignoreMap":67},[71,3397,3398,3400,3402,3404,3406,3408,3410,3413,3415,3417],{"class":73,"line":74},[71,3399,436],{"class":99},[71,3401,113],{"class":439},[71,3403,442],{"class":99},[71,3405,2845],{"class":439},[71,3407,442],{"class":99},[71,3409,446],{"class":445},[71,3411,3412],{"class":472},"timeout",[71,3414,452],{"class":445},[71,3416,2972],{"class":439},[71,3418,458],{"class":445},[71,3420,3421],{"class":73,"line":85},[71,3422,464],{"emptyLinePlaceholder":463},[71,3424,3425,3427,3429,3432,3434,3436,3438,3441,3444,3447,3449,3451,3453,3455,3457,3459,3461,3463,3465,3467,3469,3472,3474,3476,3478,3481],{"class":73,"line":106},[71,3426,2790],{"class":99},[71,3428,2793],{"class":99},[71,3430,3431],{"class":88}," slow",[71,3433,574],{"class":445},[71,3435,577],{"class":99},[71,3437,1714],{"class":99},[71,3439,3440],{"class":445},"'",[71,3442,3443],{"class":439},"static",[71,3445,3446],{"class":439}," str",[71,3448,615],{"class":445},[71,3450,113],{"class":439},[71,3452,442],{"class":99},[71,3454,2845],{"class":439},[71,3456,442],{"class":99},[71,3458,2850],{"class":88},[71,3460,475],{"class":445},[71,3462,2864],{"class":439},[71,3464,442],{"class":99},[71,3466,2869],{"class":88},[71,3468,475],{"class":445},[71,3470,3471],{"class":88},"5",[71,3473,1803],{"class":445},[71,3475,2878],{"class":99},[71,3477,3029],{"class":445},[71,3479,3480],{"class":92}," \"done\"",[71,3482,1180],{"class":445},[71,3484,3485],{"class":73,"line":121},[71,3486,464],{"emptyLinePlaceholder":463},[71,3488,3489,3491,3493,3495,3497],{"class":73,"line":259},[71,3490,469],{"class":445},[71,3492,2777],{"class":472},[71,3494,442],{"class":99},[71,3496,2782],{"class":472},[71,3498,2785],{"class":445},[71,3500,3501,3503,3505,3507,3509],{"class":73,"line":273},[71,3502,2790],{"class":99},[71,3504,2793],{"class":99},[71,3506,571],{"class":88},[71,3508,574],{"class":445},[71,3510,502],{"class":445},[71,3512,3513,3515],{"class":73,"line":286},[71,3514,595],{"class":77},[71,3516,3517],{"class":81}," timeout 是 select! 的常见封装：到点未完成就返回 Err\n",[71,3519,3520,3522,3525,3527,3529,3531,3533,3535,3537,3539,3541,3543,3545],{"class":73,"line":300},[71,3521,2531],{"class":99},[71,3523,3524],{"class":88}," timeout",[71,3526,475],{"class":445},[71,3528,2864],{"class":439},[71,3530,442],{"class":99},[71,3532,2869],{"class":88},[71,3534,475],{"class":445},[71,3536,2061],{"class":88},[71,3538,2398],{"class":445},[71,3540,3431],{"class":88},[71,3542,3044],{"class":445},[71,3544,2878],{"class":99},[71,3546,502],{"class":445},[71,3548,3549,3551,3553,3556,3558,3560,3562,3564,3566,3568,3570,3572,3574],{"class":73,"line":316},[71,3550,2634],{"class":439},[71,3552,475],{"class":445},[71,3554,3555],{"class":507},"v",[71,3557,681],{"class":445},[71,3559,2563],{"class":99},[71,3561,2566],{"class":88},[71,3563,475],{"class":445},[71,3565,698],{"class":92},[71,3567,446],{"class":445},[71,3569,3555],{"class":92},[71,3571,706],{"class":445},[71,3573,698],{"class":92},[71,3575,2269],{"class":445},[71,3577,3578,3580,3582,3585,3587,3589,3591,3593,3596,3598,3600],{"class":73,"line":330},[71,3579,2546],{"class":439},[71,3581,475],{"class":445},[71,3583,3584],{"class":507},"_",[71,3586,681],{"class":445},[71,3588,2563],{"class":99},[71,3590,2566],{"class":88},[71,3592,475],{"class":445},[71,3594,3595],{"class":92},"\"超时取消\"",[71,3597,2398],{"class":445},[71,3599,551],{"class":77},[71,3601,3602],{"class":81}," 超时时 future 被 drop，任务自然取消\n",[71,3604,3605],{"class":73,"line":352},[71,3606,1935],{"class":445},[71,3608,3609],{"class":73,"line":601},[71,3610,559],{"class":445},[14,3612,3613,3616],{},[18,3614,3615],{},"为什么 drop 就能取消？"," Rust 的 future 是惰性状态机，不轮询就不前进。drop 掉 future = 销毁状态机 = 取消。没有线程中断那样的危险操作，这就是 Rust 异步取消模型安全优雅的原因。代价是：被 drop 点必须是不变量的安全点（tokio 的锁等原语都保证了这点）。",[57,3618,3620],{"id":3619},"_35-异步文件与进程","3.5 异步文件与进程",[62,3622,3624],{"className":427,"code":3623,"language":429,"meta":67,"style":67},"use tokio::fs;\nuse tokio::io::{AsyncWriteExt, AsyncReadExt};\n\n#[tokio::main]\nasync fn main() -> std::io::Result\u003C()> {\n    \u002F\u002F 高层 API：一次性读写\n    fs::write(\"a.txt\", \"你好\").await?;\n    let s = fs::read_to_string(\"a.txt\").await?;\n\n    \u002F\u002F 低层 API：流式处理大文件\n    let mut f = fs::File::create(\"b.txt\").await?;\n    f.write_all(s.as_bytes()).await?;  \u002F\u002F 注意 trait 方法要 use AsyncWriteExt\n    f.flush().await?;\n\n    let mut buf = String::new();\n    fs::File::open(\"b.txt\").await?.read_to_string(&mut buf).await?;\n    Ok(())\n}\n",[52,3625,3626,3638,3663,3667,3679,3705,3712,3739,3763,3767,3774,3809,3840,3855,3859,3879,3916,3922],{"__ignoreMap":67},[71,3627,3628,3630,3632,3634,3636],{"class":73,"line":74},[71,3629,436],{"class":99},[71,3631,113],{"class":439},[71,3633,442],{"class":99},[71,3635,1684],{"class":472},[71,3637,687],{"class":445},[71,3639,3640,3642,3644,3646,3649,3651,3653,3656,3658,3661],{"class":73,"line":85},[71,3641,436],{"class":99},[71,3643,113],{"class":439},[71,3645,442],{"class":99},[71,3647,3648],{"class":439},"io",[71,3650,442],{"class":99},[71,3652,446],{"class":445},[71,3654,3655],{"class":439},"AsyncWriteExt",[71,3657,452],{"class":445},[71,3659,3660],{"class":439}," AsyncReadExt",[71,3662,458],{"class":445},[71,3664,3665],{"class":73,"line":106},[71,3666,464],{"emptyLinePlaceholder":463},[71,3668,3669,3671,3673,3675,3677],{"class":73,"line":121},[71,3670,469],{"class":445},[71,3672,2777],{"class":472},[71,3674,442],{"class":99},[71,3676,2782],{"class":472},[71,3678,2785],{"class":445},[71,3680,3681,3683,3685,3687,3689,3691,3693,3695,3697,3699,3701,3703],{"class":73,"line":259},[71,3682,2790],{"class":99},[71,3684,2793],{"class":99},[71,3686,571],{"class":88},[71,3688,574],{"class":445},[71,3690,577],{"class":99},[71,3692,1120],{"class":439},[71,3694,442],{"class":99},[71,3696,3648],{"class":439},[71,3698,442],{"class":99},[71,3700,585],{"class":439},[71,3702,588],{"class":445},[71,3704,502],{"class":445},[71,3706,3707,3709],{"class":73,"line":273},[71,3708,595],{"class":77},[71,3710,3711],{"class":81}," 高层 API：一次性读写\n",[71,3713,3714,3717,3719,3722,3724,3727,3729,3732,3734,3737],{"class":73,"line":286},[71,3715,3716],{"class":439},"    fs",[71,3718,442],{"class":99},[71,3720,3721],{"class":88},"write",[71,3723,475],{"class":445},[71,3725,3726],{"class":92},"\"a.txt\"",[71,3728,452],{"class":445},[71,3730,3731],{"class":92}," \"你好\"",[71,3733,681],{"class":445},[71,3735,3736],{"class":99},".await?",[71,3738,687],{"class":445},[71,3740,3741,3743,3745,3747,3749,3751,3753,3755,3757,3759,3761],{"class":73,"line":300},[71,3742,604],{"class":99},[71,3744,661],{"class":507},[71,3746,610],{"class":99},[71,3748,1758],{"class":439},[71,3750,442],{"class":99},[71,3752,1763],{"class":88},[71,3754,475],{"class":445},[71,3756,3726],{"class":92},[71,3758,681],{"class":445},[71,3760,3736],{"class":99},[71,3762,687],{"class":445},[71,3764,3765],{"class":73,"line":316},[71,3766,464],{"emptyLinePlaceholder":463},[71,3768,3769,3771],{"class":73,"line":330},[71,3770,595],{"class":77},[71,3772,3773],{"class":81}," 低层 API：流式处理大文件\n",[71,3775,3776,3778,3781,3784,3786,3788,3790,3793,3795,3798,3800,3803,3805,3807],{"class":73,"line":352},[71,3777,604],{"class":99},[71,3779,3780],{"class":99}," mut",[71,3782,3783],{"class":507}," f",[71,3785,610],{"class":99},[71,3787,1758],{"class":439},[71,3789,442],{"class":99},[71,3791,3792],{"class":439},"File",[71,3794,442],{"class":99},[71,3796,3797],{"class":88},"create",[71,3799,475],{"class":445},[71,3801,3802],{"class":92},"\"b.txt\"",[71,3804,681],{"class":445},[71,3806,3736],{"class":99},[71,3808,687],{"class":445},[71,3810,3811,3814,3816,3819,3821,3823,3825,3828,3830,3832,3834,3837],{"class":73,"line":601},[71,3812,3813],{"class":507},"    f",[71,3815,626],{"class":99},[71,3817,3818],{"class":88},"write_all",[71,3820,475],{"class":445},[71,3822,703],{"class":507},[71,3824,626],{"class":99},[71,3826,3827],{"class":88},"as_bytes",[71,3829,3044],{"class":445},[71,3831,3736],{"class":99},[71,3833,3029],{"class":445},[71,3835,3836],{"class":77},"  \u002F\u002F",[71,3838,3839],{"class":81}," 注意 trait 方法要 use AsyncWriteExt\n",[71,3841,3842,3844,3846,3849,3851,3853],{"class":73,"line":656},[71,3843,3813],{"class":507},[71,3845,626],{"class":99},[71,3847,3848],{"class":88},"flush",[71,3850,574],{"class":445},[71,3852,3736],{"class":99},[71,3854,687],{"class":445},[71,3856,3857],{"class":73,"line":690},[71,3858,464],{"emptyLinePlaceholder":463},[71,3860,3861,3863,3865,3868,3870,3872,3874,3877],{"class":73,"line":719},[71,3862,604],{"class":99},[71,3864,3780],{"class":99},[71,3866,3867],{"class":507}," buf",[71,3869,610],{"class":99},[71,3871,514],{"class":439},[71,3873,442],{"class":99},[71,3875,3876],{"class":88},"new",[71,3878,1499],{"class":445},[71,3880,3881,3883,3885,3887,3889,3892,3894,3896,3898,3901,3903,3905,3908,3910,3912,3914],{"class":73,"line":724},[71,3882,3716],{"class":439},[71,3884,442],{"class":99},[71,3886,3792],{"class":439},[71,3888,442],{"class":99},[71,3890,3891],{"class":88},"open",[71,3893,475],{"class":445},[71,3895,3802],{"class":92},[71,3897,681],{"class":445},[71,3899,3900],{"class":99},".await?.",[71,3902,1763],{"class":88},[71,3904,475],{"class":445},[71,3906,3907],{"class":99},"&mut",[71,3909,3867],{"class":507},[71,3911,681],{"class":445},[71,3913,3736],{"class":99},[71,3915,687],{"class":445},[71,3917,3918,3920],{"class":73,"line":732},[71,3919,851],{"class":439},[71,3921,854],{"class":445},[71,3923,3924],{"class":73,"line":768},[71,3925,559],{"class":445},[11,3927,3928],{},[14,3929,3930,3931,140],{},"注意：tokio 的异步文件 IO 底层其实是线程池（操作系统对普通文件没有真正的异步接口）。",[18,3932,3933,3934],{},"CPU 密集或必须用阻塞 API 的活，用 ",[52,3935,3936],{},"tokio::task::spawn_blocking",[62,3938,3940],{"className":427,"code":3939,"language":429,"meta":67,"style":67},"let hash = tokio::task::spawn_blocking(|| {\n    \u002F\u002F 这里跑同步的重计算，不会卡住异步调度器的工作线程\n    expensive_hash()\n}).await.unwrap();\n",[52,3941,3942,3969,3976,3984],{"__ignoreMap":67},[71,3943,3944,3946,3949,3951,3953,3955,3958,3960,3963,3965,3967],{"class":73,"line":74},[71,3945,1241],{"class":99},[71,3947,3948],{"class":507}," hash",[71,3950,610],{"class":99},[71,3952,113],{"class":439},[71,3954,442],{"class":99},[71,3956,3957],{"class":439},"task",[71,3959,442],{"class":99},[71,3961,3962],{"class":88},"spawn_blocking",[71,3964,475],{"class":445},[71,3966,1783],{"class":99},[71,3968,502],{"class":445},[71,3970,3971,3973],{"class":73,"line":85},[71,3972,595],{"class":77},[71,3974,3975],{"class":81}," 这里跑同步的重计算，不会卡住异步调度器的工作线程\n",[71,3977,3978,3981],{"class":73,"line":106},[71,3979,3980],{"class":88},"    expensive_hash",[71,3982,3983],{"class":445},"()\n",[71,3985,3986,3989,3991,3993],{"class":73,"line":121},[71,3987,3988],{"class":445},"})",[71,3990,2915],{"class":99},[71,3992,1496],{"class":88},[71,3994,1499],{"class":445},[14,3996,3997,4000,4001,4004,4005,375],{},[18,3998,3999],{},"为什么阻塞代码会卡住整个程序？"," tokio 工作线程数量有限（默认 = CPU 核数）。一个任务在工作线程里做阻塞调用（",[52,4002,4003],{},"std::thread::sleep","、同步文件 IO、重计算），这个线程就停摆了，它手上其他几百个异步任务全部挨饿。所以铁律：",[18,4006,4007],{},"async 代码里绝不调用阻塞 API",[57,4009,4011],{"id":4010},"_36-异步同步原语","3.6 异步同步原语",[62,4013,4015],{"className":427,"code":4014,"language":429,"meta":67,"style":67},"use std::sync::Arc;\nuse tokio::sync::{Mutex, mpsc, oneshot, RwLock, Semaphore, Notify};\n\n#[tokio::main]\nasync fn main() {\n    \u002F\u002F 1) tokio::sync::Mutex：跨 .await 持锁时必须用它（std::Mutex 的守卫不是 Send，且会阻塞线程）\n    let counter = Arc::new(Mutex::new(0u64));\n    let mut handles = vec![];\n    for _ in 0..10 {\n        let c = counter.clone();\n        handles.push(tokio::spawn(async move {\n            let mut n = c.lock().await; \u002F\u002F 异步锁，拿不到时让出线程而不是阻塞\n            *n += 1;\n        }));\n    }\n    for h in handles { h.await.unwrap(); }\n    println!(\"{}\", *counter.lock().await); \u002F\u002F 10\n\n    \u002F\u002F 2) mpsc 通道：多生产者单消费者，任务间通信的首选\n    let (tx, mut rx) = mpsc::channel::\u003CString>(100); \u002F\u002F 100 是缓冲容量，满了 send 会 .await 背压\n    tokio::spawn(async move {\n        for i in 0..3 {\n            tx.send(format!(\"消息{i}\")).await.unwrap();\n        }\n    });\n    while let Some(msg) = rx.recv().await { \u002F\u002F 所有 tx 都 drop 后 recv 返回 None，循环结束\n        println!(\"收到: {msg}\");\n    }\n\n    \u002F\u002F 3) oneshot：一次性应答，常用于「请求-响应」式取消通知\n    let (tx, rx) = oneshot::channel::\u003C&str>();\n    tokio::spawn(async move { let _ = tx.send(\"pong\"); });\n    println!(\"{}\", rx.await.unwrap());\n\n    \u002F\u002F 4) RwLock \u002F Semaphore \u002F Notify 与 std 对应物语义类似，但都是 async 版\n    let sem = Arc::new(Semaphore::new(3)); \u002F\u002F 限制并发度：最多 3 个任务同时进入临界区\n    let permit = sem.acquire().await.unwrap();\n    drop(permit); \u002F\u002F 释放名额\n    let _ = Notify::new(); \u002F\u002F 事件通知：notify_one\u002Fnotify_waiters + notified().await\n    let _ = RwLock::new(0u8);\n}\n",[52,4016,4017,4035,4079,4083,4095,4107,4114,4147,4164,4183,4202,4229,4259,4275,4280,4284,4308,4343,4347,4354,4400,4416,4435,4472,4477,4481,4517,4537,4541,4545,4552,4585,4624,4647,4652,4660,4696,4721,4739,4761,4785],{"__ignoreMap":67},[71,4018,4019,4021,4023,4025,4028,4030,4033],{"class":73,"line":74},[71,4020,436],{"class":99},[71,4022,1120],{"class":439},[71,4024,442],{"class":99},[71,4026,4027],{"class":439},"sync",[71,4029,442],{"class":99},[71,4031,4032],{"class":439},"Arc",[71,4034,687],{"class":445},[71,4036,4037,4039,4041,4043,4045,4047,4049,4052,4054,4057,4059,4062,4064,4067,4069,4072,4074,4077],{"class":73,"line":85},[71,4038,436],{"class":99},[71,4040,113],{"class":439},[71,4042,442],{"class":99},[71,4044,4027],{"class":439},[71,4046,442],{"class":99},[71,4048,446],{"class":445},[71,4050,4051],{"class":439},"Mutex",[71,4053,452],{"class":445},[71,4055,4056],{"class":472}," mpsc",[71,4058,452],{"class":445},[71,4060,4061],{"class":472}," oneshot",[71,4063,452],{"class":445},[71,4065,4066],{"class":439}," RwLock",[71,4068,452],{"class":445},[71,4070,4071],{"class":439}," Semaphore",[71,4073,452],{"class":445},[71,4075,4076],{"class":439}," Notify",[71,4078,458],{"class":445},[71,4080,4081],{"class":73,"line":106},[71,4082,464],{"emptyLinePlaceholder":463},[71,4084,4085,4087,4089,4091,4093],{"class":73,"line":121},[71,4086,469],{"class":445},[71,4088,2777],{"class":472},[71,4090,442],{"class":99},[71,4092,2782],{"class":472},[71,4094,2785],{"class":445},[71,4096,4097,4099,4101,4103,4105],{"class":73,"line":259},[71,4098,2790],{"class":99},[71,4100,2793],{"class":99},[71,4102,571],{"class":88},[71,4104,574],{"class":445},[71,4106,502],{"class":445},[71,4108,4109,4111],{"class":73,"line":273},[71,4110,595],{"class":77},[71,4112,4113],{"class":81}," 1) tokio::sync::Mutex：跨 .await 持锁时必须用它（std::Mutex 的守卫不是 Send，且会阻塞线程）\n",[71,4115,4116,4118,4121,4123,4126,4128,4130,4132,4134,4136,4138,4140,4142,4144],{"class":73,"line":286},[71,4117,604],{"class":99},[71,4119,4120],{"class":507}," counter",[71,4122,610],{"class":99},[71,4124,4125],{"class":439}," Arc",[71,4127,442],{"class":99},[71,4129,3876],{"class":88},[71,4131,475],{"class":445},[71,4133,4051],{"class":439},[71,4135,442],{"class":99},[71,4137,3876],{"class":88},[71,4139,475],{"class":445},[71,4141,1329],{"class":88},[71,4143,2266],{"class":439},[71,4145,4146],{"class":445},"));\n",[71,4148,4149,4151,4153,4156,4158,4161],{"class":73,"line":300},[71,4150,604],{"class":99},[71,4152,3780],{"class":99},[71,4154,4155],{"class":507}," handles",[71,4157,610],{"class":99},[71,4159,4160],{"class":88}," vec!",[71,4162,4163],{"class":445},"[];\n",[71,4165,4166,4169,4171,4174,4176,4179,4181],{"class":73,"line":316},[71,4167,4168],{"class":99},"    for",[71,4170,3350],{"class":507},[71,4172,4173],{"class":99}," in",[71,4175,1868],{"class":88},[71,4177,4178],{"class":99},"..",[71,4180,3326],{"class":88},[71,4182,502],{"class":445},[71,4184,4185,4188,4191,4193,4195,4197,4200],{"class":73,"line":330},[71,4186,4187],{"class":99},"        let",[71,4189,4190],{"class":507}," c",[71,4192,610],{"class":99},[71,4194,4120],{"class":507},[71,4196,626],{"class":99},[71,4198,4199],{"class":88},"clone",[71,4201,1499],{"class":445},[71,4203,4204,4207,4209,4212,4214,4216,4218,4220,4222,4224,4227],{"class":73,"line":352},[71,4205,4206],{"class":507},"        handles",[71,4208,626],{"class":99},[71,4210,4211],{"class":88},"push",[71,4213,475],{"class":445},[71,4215,2777],{"class":439},[71,4217,442],{"class":99},[71,4219,2814],{"class":88},[71,4221,475],{"class":445},[71,4223,2790],{"class":99},[71,4225,4226],{"class":99}," move",[71,4228,502],{"class":445},[71,4230,4231,4234,4236,4239,4241,4243,4245,4248,4250,4252,4254,4256],{"class":73,"line":601},[71,4232,4233],{"class":99},"            let",[71,4235,3780],{"class":99},[71,4237,4238],{"class":507}," n",[71,4240,610],{"class":99},[71,4242,4190],{"class":507},[71,4244,626],{"class":99},[71,4246,4247],{"class":88},"lock",[71,4249,574],{"class":445},[71,4251,2878],{"class":99},[71,4253,3029],{"class":445},[71,4255,551],{"class":77},[71,4257,4258],{"class":81}," 异步锁，拿不到时让出线程而不是阻塞\n",[71,4260,4261,4264,4267,4270,4273],{"class":73,"line":656},[71,4262,4263],{"class":99},"            *",[71,4265,4266],{"class":507},"n",[71,4268,4269],{"class":99}," +=",[71,4271,4272],{"class":88}," 1",[71,4274,687],{"class":445},[71,4276,4277],{"class":73,"line":690},[71,4278,4279],{"class":445},"        }));\n",[71,4281,4282],{"class":73,"line":719},[71,4283,1935],{"class":445},[71,4285,4286,4288,4291,4293,4295,4297,4299,4301,4303,4306],{"class":73,"line":724},[71,4287,4168],{"class":99},[71,4289,4290],{"class":507}," h",[71,4292,4173],{"class":99},[71,4294,4155],{"class":507},[71,4296,615],{"class":445},[71,4298,4290],{"class":507},[71,4300,2915],{"class":99},[71,4302,1496],{"class":88},[71,4304,4305],{"class":445},"();",[71,4307,1180],{"class":445},[71,4309,4310,4312,4314,4316,4318,4320,4322,4325,4328,4330,4332,4334,4336,4338,4340],{"class":73,"line":732},[71,4311,693],{"class":88},[71,4313,475],{"class":445},[71,4315,698],{"class":92},[71,4317,741],{"class":445},[71,4319,698],{"class":92},[71,4321,452],{"class":445},[71,4323,4324],{"class":99}," *",[71,4326,4327],{"class":507},"counter",[71,4329,626],{"class":99},[71,4331,4247],{"class":88},[71,4333,574],{"class":445},[71,4335,2878],{"class":99},[71,4337,711],{"class":445},[71,4339,551],{"class":77},[71,4341,4342],{"class":81}," 10\n",[71,4344,4345],{"class":73,"line":768},[71,4346,464],{"emptyLinePlaceholder":463},[71,4348,4349,4351],{"class":73,"line":773},[71,4350,595],{"class":77},[71,4352,4353],{"class":81}," 2) mpsc 通道：多生产者单消费者，任务间通信的首选\n",[71,4355,4356,4358,4360,4363,4365,4367,4370,4372,4374,4376,4378,4381,4383,4385,4387,4390,4393,4395,4397],{"class":73,"line":781},[71,4357,604],{"class":99},[71,4359,3162],{"class":445},[71,4361,4362],{"class":507},"tx",[71,4364,452],{"class":445},[71,4366,3780],{"class":99},[71,4368,4369],{"class":507}," rx",[71,4371,681],{"class":445},[71,4373,610],{"class":99},[71,4375,4056],{"class":439},[71,4377,442],{"class":99},[71,4379,4380],{"class":88},"channel",[71,4382,442],{"class":99},[71,4384,542],{"class":445},[71,4386,545],{"class":439},[71,4388,4389],{"class":445},">(",[71,4391,4392],{"class":88},"100",[71,4394,711],{"class":445},[71,4396,551],{"class":77},[71,4398,4399],{"class":81}," 100 是缓冲容量，满了 send 会 .await 背压\n",[71,4401,4402,4404,4406,4408,4410,4412,4414],{"class":73,"line":796},[71,4403,3268],{"class":439},[71,4405,442],{"class":99},[71,4407,2814],{"class":88},[71,4409,475],{"class":445},[71,4411,2790],{"class":99},[71,4413,4226],{"class":99},[71,4415,502],{"class":445},[71,4417,4418,4421,4424,4426,4428,4430,4433],{"class":73,"line":828},[71,4419,4420],{"class":99},"        for",[71,4422,4423],{"class":507}," i",[71,4425,4173],{"class":99},[71,4427,1868],{"class":88},[71,4429,4178],{"class":99},[71,4431,4432],{"class":88},"3",[71,4434,502],{"class":445},[71,4436,4437,4440,4442,4445,4447,4450,4452,4455,4457,4460,4462,4464,4466,4468,4470],{"class":73,"line":848},[71,4438,4439],{"class":507},"            tx",[71,4441,626],{"class":99},[71,4443,4444],{"class":88},"send",[71,4446,475],{"class":445},[71,4448,4449],{"class":88},"format!",[71,4451,475],{"class":445},[71,4453,4454],{"class":92},"\"消息",[71,4456,446],{"class":445},[71,4458,4459],{"class":92},"i",[71,4461,706],{"class":445},[71,4463,698],{"class":92},[71,4465,1803],{"class":445},[71,4467,2915],{"class":99},[71,4469,1496],{"class":88},[71,4471,1499],{"class":445},[71,4473,4474],{"class":73,"line":857},[71,4475,4476],{"class":445},"        }\n",[71,4478,4479],{"class":73,"line":2022},[71,4480,2890],{"class":445},[71,4482,4483,4486,4488,4490,4492,4495,4497,4499,4501,4503,4506,4508,4510,4512,4514],{"class":73,"line":2027},[71,4484,4485],{"class":99},"    while",[71,4487,1298],{"class":99},[71,4489,1301],{"class":439},[71,4491,475],{"class":445},[71,4493,4494],{"class":507},"msg",[71,4496,681],{"class":445},[71,4498,610],{"class":99},[71,4500,4369],{"class":507},[71,4502,626],{"class":99},[71,4504,4505],{"class":88},"recv",[71,4507,574],{"class":445},[71,4509,2878],{"class":99},[71,4511,615],{"class":445},[71,4513,551],{"class":77},[71,4515,4516],{"class":81}," 所有 tx 都 drop 后 recv 返回 None，循环结束\n",[71,4518,4519,4522,4524,4527,4529,4531,4533,4535],{"class":73,"line":2035},[71,4520,4521],{"class":88},"        println!",[71,4523,475],{"class":445},[71,4525,4526],{"class":92},"\"收到: ",[71,4528,446],{"class":445},[71,4530,4494],{"class":92},[71,4532,706],{"class":445},[71,4534,698],{"class":92},[71,4536,765],{"class":445},[71,4538,4539],{"class":73,"line":2068},[71,4540,1935],{"class":445},[71,4542,4543],{"class":73,"line":2095},[71,4544,464],{"emptyLinePlaceholder":463},[71,4546,4547,4549],{"class":73,"line":2131},[71,4548,595],{"class":77},[71,4550,4551],{"class":81}," 3) oneshot：一次性应答，常用于「请求-响应」式取消通知\n",[71,4553,4554,4556,4558,4560,4562,4564,4566,4568,4570,4572,4574,4576,4578,4580,4582],{"class":73,"line":2136},[71,4555,604],{"class":99},[71,4557,3162],{"class":445},[71,4559,4362],{"class":507},[71,4561,452],{"class":445},[71,4563,4369],{"class":507},[71,4565,681],{"class":445},[71,4567,610],{"class":99},[71,4569,4061],{"class":439},[71,4571,442],{"class":99},[71,4573,4380],{"class":88},[71,4575,442],{"class":99},[71,4577,542],{"class":445},[71,4579,675],{"class":99},[71,4581,1717],{"class":439},[71,4583,4584],{"class":445},">();\n",[71,4586,4587,4589,4591,4593,4595,4597,4599,4601,4603,4605,4607,4610,4612,4614,4616,4619,4621],{"class":73,"line":2143},[71,4588,3268],{"class":439},[71,4590,442],{"class":99},[71,4592,2814],{"class":88},[71,4594,475],{"class":445},[71,4596,2790],{"class":99},[71,4598,4226],{"class":99},[71,4600,615],{"class":445},[71,4602,1298],{"class":99},[71,4604,3350],{"class":507},[71,4606,610],{"class":99},[71,4608,4609],{"class":507}," tx",[71,4611,626],{"class":99},[71,4613,4444],{"class":88},[71,4615,475],{"class":445},[71,4617,4618],{"class":92},"\"pong\"",[71,4620,711],{"class":445},[71,4622,4623],{"class":445}," });\n",[71,4625,4627,4629,4631,4633,4635,4637,4639,4641,4643,4645],{"class":73,"line":4626},33,[71,4628,693],{"class":88},[71,4630,475],{"class":445},[71,4632,698],{"class":92},[71,4634,741],{"class":445},[71,4636,698],{"class":92},[71,4638,452],{"class":445},[71,4640,4369],{"class":507},[71,4642,2915],{"class":99},[71,4644,1496],{"class":88},[71,4646,2019],{"class":445},[71,4648,4650],{"class":73,"line":4649},34,[71,4651,464],{"emptyLinePlaceholder":463},[71,4653,4655,4657],{"class":73,"line":4654},35,[71,4656,595],{"class":77},[71,4658,4659],{"class":81}," 4) RwLock \u002F Semaphore \u002F Notify 与 std 对应物语义类似，但都是 async 版\n",[71,4661,4663,4665,4668,4670,4672,4674,4676,4678,4681,4683,4685,4687,4689,4691,4693],{"class":73,"line":4662},36,[71,4664,604],{"class":99},[71,4666,4667],{"class":507}," sem",[71,4669,610],{"class":99},[71,4671,4125],{"class":439},[71,4673,442],{"class":99},[71,4675,3876],{"class":88},[71,4677,475],{"class":445},[71,4679,4680],{"class":439},"Semaphore",[71,4682,442],{"class":99},[71,4684,3876],{"class":88},[71,4686,475],{"class":445},[71,4688,4432],{"class":88},[71,4690,2482],{"class":445},[71,4692,551],{"class":77},[71,4694,4695],{"class":81}," 限制并发度：最多 3 个任务同时进入临界区\n",[71,4697,4699,4701,4704,4706,4708,4710,4713,4715,4717,4719],{"class":73,"line":4698},37,[71,4700,604],{"class":99},[71,4702,4703],{"class":507}," permit",[71,4705,610],{"class":99},[71,4707,4667],{"class":507},[71,4709,626],{"class":99},[71,4711,4712],{"class":88},"acquire",[71,4714,574],{"class":445},[71,4716,2915],{"class":99},[71,4718,1496],{"class":88},[71,4720,1499],{"class":445},[71,4722,4724,4727,4729,4732,4734,4736],{"class":73,"line":4723},38,[71,4725,4726],{"class":88},"    drop",[71,4728,475],{"class":445},[71,4730,4731],{"class":507},"permit",[71,4733,711],{"class":445},[71,4735,551],{"class":77},[71,4737,4738],{"class":81}," 释放名额\n",[71,4740,4742,4744,4746,4748,4750,4752,4754,4756,4758],{"class":73,"line":4741},39,[71,4743,604],{"class":99},[71,4745,3350],{"class":507},[71,4747,610],{"class":99},[71,4749,4076],{"class":439},[71,4751,442],{"class":99},[71,4753,3876],{"class":88},[71,4755,4305],{"class":445},[71,4757,551],{"class":77},[71,4759,4760],{"class":81}," 事件通知：notify_one\u002Fnotify_waiters + notified().await\n",[71,4762,4764,4766,4768,4770,4772,4774,4776,4778,4780,4783],{"class":73,"line":4763},40,[71,4765,604],{"class":99},[71,4767,3350],{"class":507},[71,4769,610],{"class":99},[71,4771,4066],{"class":439},[71,4773,442],{"class":99},[71,4775,3876],{"class":88},[71,4777,475],{"class":445},[71,4779,1329],{"class":88},[71,4781,4782],{"class":439},"u8",[71,4784,765],{"class":445},[71,4786,4788],{"class":73,"line":4787},41,[71,4789,559],{"class":445},[14,4791,4792,4795,4796,4799,4800,4802],{},[18,4793,4794],{},"为什么通道容量要有限（100 而不是 unbounded）？"," 有限缓冲提供了",[18,4797,4798],{},"背压（backpressure）","：生产快于消费时，",[52,4801,4444],{}," 会挂起等位置空出来，内存不会无限膨胀。无界通道在流量高峰时就是内存炸弹。这是 tokio 引导你写出稳态系统的典型设计。",[23,4804],{},[26,4806,4808],{"id":4807},"第-4-章-reqwesthttp-客户端","第 4 章 reqwest：HTTP 客户端",[57,4810,4812],{"id":4811},"_41-它解决什么问题","4.1 它解决什么问题",[14,4814,4815],{},"发 HTTP 请求调用 REST API、下载文件。reqwest 构建在 tokio + hyper 之上，提供符合直觉的高层 API，是生态绝对主流。",[62,4817,4819],{"className":143,"code":4818,"language":145,"meta":67,"style":67},"[dependencies]\nreqwest = { version = \"0.12\", features = [\"json\"] }\ntokio = { version = \"1\", features = [\"full\"] }\nserde = { version = \"1\", features = [\"derive\"] }\n",[52,4820,4821,4825,4829,4833],{"__ignoreMap":67},[71,4822,4823],{"class":73,"line":74},[71,4824,152],{},[71,4826,4827],{"class":73,"line":85},[71,4828,167],{},[71,4830,4831],{"class":73,"line":106},[71,4832,162],{},[71,4834,4835],{"class":73,"line":121},[71,4836,157],{},[57,4838,4840],{"id":4839},"_42-get-post-与-json","4.2 GET \u002F POST 与 JSON",[62,4842,4844],{"className":427,"code":4843,"language":429,"meta":67,"style":67},"use serde::{Deserialize, Serialize};\n\n#[derive(Deserialize, Debug)]\nstruct Post { id: u64, title: String }\n\n#[derive(Serialize)]\nstruct NewPost { title: String, body: String }\n\n#[tokio::main]\nasync fn main() -> reqwest::Result\u003C()> {\n    \u002F\u002F 最简单的 GET\n    let body = reqwest::get(\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F1\")\n        .await?\n        .text()          \u002F\u002F .text() 拿字符串；.bytes() 拿字节；.json() 直接反序列化\n        .await?;\n    println!(\"原始响应: {}...\", &body[..50.min(body.len())]);\n\n    \u002F\u002F GET + 反序列化为结构体（依赖 serde）\n    let post: Post = reqwest::get(\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F2\")\n        .await?\n        .json()\n        .await?;\n    println!(\"{post:?}\");\n    Ok(())\n}\n",[52,4845,4846,4864,4868,4884,4910,4914,4926,4952,4956,4968,4990,4997,5019,5024,5038,5045,5089,5093,5100,5126,5130,5139,5145,5164,5170],{"__ignoreMap":67},[71,4847,4848,4850,4852,4854,4856,4858,4860,4862],{"class":73,"line":74},[71,4849,436],{"class":99},[71,4851,96],{"class":439},[71,4853,442],{"class":99},[71,4855,446],{"class":445},[71,4857,449],{"class":439},[71,4859,452],{"class":445},[71,4861,455],{"class":439},[71,4863,458],{"class":445},[71,4865,4866],{"class":73,"line":85},[71,4867,464],{"emptyLinePlaceholder":463},[71,4869,4870,4872,4874,4876,4878,4880,4882],{"class":73,"line":106},[71,4871,469],{"class":445},[71,4873,176],{"class":472},[71,4875,475],{"class":445},[71,4877,449],{"class":439},[71,4879,452],{"class":445},[71,4881,488],{"class":439},[71,4883,491],{"class":445},[71,4885,4886,4888,4891,4893,4895,4897,4899,4901,4904,4906,4908],{"class":73,"line":121},[71,4887,496],{"class":99},[71,4889,4890],{"class":439}," Post",[71,4892,615],{"class":445},[71,4894,2453],{"class":507},[71,4896,511],{"class":99},[71,4898,931],{"class":439},[71,4900,452],{"class":445},[71,4902,4903],{"class":507}," title",[71,4905,511],{"class":99},[71,4907,514],{"class":439},[71,4909,1180],{"class":445},[71,4911,4912],{"class":73,"line":259},[71,4913,464],{"emptyLinePlaceholder":463},[71,4915,4916,4918,4920,4922,4924],{"class":73,"line":273},[71,4917,469],{"class":445},[71,4919,176],{"class":472},[71,4921,475],{"class":445},[71,4923,478],{"class":439},[71,4925,491],{"class":445},[71,4927,4928,4930,4933,4935,4937,4939,4941,4943,4946,4948,4950],{"class":73,"line":286},[71,4929,496],{"class":99},[71,4931,4932],{"class":439}," NewPost",[71,4934,615],{"class":445},[71,4936,4903],{"class":507},[71,4938,511],{"class":99},[71,4940,514],{"class":439},[71,4942,452],{"class":445},[71,4944,4945],{"class":507}," body",[71,4947,511],{"class":99},[71,4949,514],{"class":439},[71,4951,1180],{"class":445},[71,4953,4954],{"class":73,"line":300},[71,4955,464],{"emptyLinePlaceholder":463},[71,4957,4958,4960,4962,4964,4966],{"class":73,"line":316},[71,4959,469],{"class":445},[71,4961,2777],{"class":472},[71,4963,442],{"class":99},[71,4965,2782],{"class":472},[71,4967,2785],{"class":445},[71,4969,4970,4972,4974,4976,4978,4980,4982,4984,4986,4988],{"class":73,"line":330},[71,4971,2790],{"class":99},[71,4973,2793],{"class":99},[71,4975,571],{"class":88},[71,4977,574],{"class":445},[71,4979,577],{"class":99},[71,4981,128],{"class":439},[71,4983,442],{"class":99},[71,4985,585],{"class":439},[71,4987,588],{"class":445},[71,4989,502],{"class":445},[71,4991,4992,4994],{"class":73,"line":352},[71,4993,595],{"class":77},[71,4995,4996],{"class":81}," 最简单的 GET\n",[71,4998,4999,5001,5003,5005,5007,5009,5012,5014,5017],{"class":73,"line":601},[71,5000,604],{"class":99},[71,5002,4945],{"class":507},[71,5004,610],{"class":99},[71,5006,128],{"class":439},[71,5008,442],{"class":99},[71,5010,5011],{"class":88},"get",[71,5013,475],{"class":445},[71,5015,5016],{"class":92},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F1\"",[71,5018,1770],{"class":445},[71,5020,5021],{"class":73,"line":656},[71,5022,5023],{"class":99},"        .await?\n",[71,5025,5026,5028,5031,5033,5035],{"class":73,"line":690},[71,5027,1775],{"class":99},[71,5029,5030],{"class":88},"text",[71,5032,574],{"class":445},[71,5034,1032],{"class":77},[71,5036,5037],{"class":81}," .text() 拿字符串；.bytes() 拿字节；.json() 直接反序列化\n",[71,5039,5040,5043],{"class":73,"line":719},[71,5041,5042],{"class":99},"        .await?",[71,5044,687],{"class":445},[71,5046,5047,5049,5051,5054,5056,5059,5061,5063,5066,5068,5070,5073,5075,5078,5080,5082,5084,5086],{"class":73,"line":724},[71,5048,693],{"class":88},[71,5050,475],{"class":445},[71,5052,5053],{"class":92},"\"原始响应: ",[71,5055,741],{"class":445},[71,5057,5058],{"class":92},"...\"",[71,5060,452],{"class":445},[71,5062,1714],{"class":99},[71,5064,5065],{"class":507},"body",[71,5067,1315],{"class":445},[71,5069,4178],{"class":99},[71,5071,5072],{"class":88},"50",[71,5074,626],{"class":445},[71,5076,5077],{"class":88},"min",[71,5079,475],{"class":445},[71,5081,5065],{"class":507},[71,5083,626],{"class":99},[71,5085,2016],{"class":88},[71,5087,5088],{"class":445},"())]);\n",[71,5090,5091],{"class":73,"line":732},[71,5092,464],{"emptyLinePlaceholder":463},[71,5094,5095,5097],{"class":73,"line":768},[71,5096,595],{"class":77},[71,5098,5099],{"class":81}," GET + 反序列化为结构体（依赖 serde）\n",[71,5101,5102,5104,5107,5109,5111,5113,5115,5117,5119,5121,5124],{"class":73,"line":773},[71,5103,604],{"class":99},[71,5105,5106],{"class":507}," post",[71,5108,511],{"class":99},[71,5110,4890],{"class":439},[71,5112,610],{"class":99},[71,5114,128],{"class":439},[71,5116,442],{"class":99},[71,5118,5011],{"class":88},[71,5120,475],{"class":445},[71,5122,5123],{"class":92},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F2\"",[71,5125,1770],{"class":445},[71,5127,5128],{"class":73,"line":781},[71,5129,5023],{"class":99},[71,5131,5132,5134,5137],{"class":73,"line":796},[71,5133,1775],{"class":99},[71,5135,5136],{"class":88},"json",[71,5138,3983],{"class":445},[71,5140,5141,5143],{"class":73,"line":828},[71,5142,5042],{"class":99},[71,5144,687],{"class":445},[71,5146,5147,5149,5151,5153,5155,5158,5160,5162],{"class":73,"line":848},[71,5148,693],{"class":88},[71,5150,475],{"class":445},[71,5152,698],{"class":92},[71,5154,446],{"class":445},[71,5156,5157],{"class":92},"post:?",[71,5159,706],{"class":445},[71,5161,698],{"class":92},[71,5163,765],{"class":445},[71,5165,5166,5168],{"class":73,"line":857},[71,5167,851],{"class":439},[71,5169,854],{"class":445},[71,5171,5172],{"class":73,"line":2022},[71,5173,559],{"class":445},[57,5175,5177],{"id":5176},"_43-复用-client生产环境的正确姿势","4.3 复用 Client：生产环境的正确姿势",[62,5179,5181],{"className":427,"code":5180,"language":429,"meta":67,"style":67},"use std::time::Duration;\n\n#[tokio::main]\nasync fn main() -> reqwest::Result\u003C()> {\n    \u002F\u002F 为什么要显式建 Client 而不是每次 reqwest::get？\n    \u002F\u002F Client 内部维护连接池（HTTP keep-alive），复用 TCP\u002FTLS 连接，\n    \u002F\u002F 高频请求下性能差距巨大；还能统一配置超时、默认 header、代理。\n    let client = reqwest::Client::builder()\n        .timeout(Duration::from_secs(10))            \u002F\u002F 总超时，防止请求永远挂起拖垮服务\n        .connect_timeout(Duration::from_secs(3))     \u002F\u002F 连接建立超时单独设置，更快失败\n        .user_agent(\"my-app\u002F1.0\")                    \u002F\u002F 很多 API 拒绝无 UA 的请求\n        .build()?;\n\n    \u002F\u002F POST JSON\n    let resp = client\n        .post(\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\")\n        .json(&NewPost { title: \"t\".into(), body: \"b\".into() }) \u002F\u002F 自动序列化 + 设 Content-Type\n        .send()\n        .await?;\n    println!(\"状态: {}\", resp.status());\n\n    \u002F\u002F GET 带查询参数、自定义 header\n    let resp = client\n        .get(\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\")\n        .query(&[(\"userId\", \"1\")])                   \u002F\u002F 自动做 URL 编码，别手拼字符串\n        .header(\"Authorization\", \"Bearer token123\")\n        .send()\n        .await?;\n\n    \u002F\u002F error_for_status：把 4xx\u002F5xx 变成 Err，否则 reqwest 认为 HTTP 层成功就是成功\n    let posts: Vec\u003Cserde_json::Value> = resp.error_for_status()?.json().await?;\n    println!(\"拿到 {} 条\", posts.len());\n    Ok(())\n}\n",[52,5182,5183,5199,5203,5215,5237,5244,5251,5258,5281,5306,5333,5353,5366,5370,5377,5389,5403,5452,5460,5466,5490,5494,5501,5511,5523,5553,5572,5580,5586,5590,5597,5641,5665,5671],{"__ignoreMap":67},[71,5184,5185,5187,5189,5191,5193,5195,5197],{"class":73,"line":74},[71,5186,436],{"class":99},[71,5188,1120],{"class":439},[71,5190,442],{"class":99},[71,5192,2845],{"class":439},[71,5194,442],{"class":99},[71,5196,2864],{"class":439},[71,5198,687],{"class":445},[71,5200,5201],{"class":73,"line":85},[71,5202,464],{"emptyLinePlaceholder":463},[71,5204,5205,5207,5209,5211,5213],{"class":73,"line":106},[71,5206,469],{"class":445},[71,5208,2777],{"class":472},[71,5210,442],{"class":99},[71,5212,2782],{"class":472},[71,5214,2785],{"class":445},[71,5216,5217,5219,5221,5223,5225,5227,5229,5231,5233,5235],{"class":73,"line":121},[71,5218,2790],{"class":99},[71,5220,2793],{"class":99},[71,5222,571],{"class":88},[71,5224,574],{"class":445},[71,5226,577],{"class":99},[71,5228,128],{"class":439},[71,5230,442],{"class":99},[71,5232,585],{"class":439},[71,5234,588],{"class":445},[71,5236,502],{"class":445},[71,5238,5239,5241],{"class":73,"line":259},[71,5240,595],{"class":77},[71,5242,5243],{"class":81}," 为什么要显式建 Client 而不是每次 reqwest::get？\n",[71,5245,5246,5248],{"class":73,"line":273},[71,5247,595],{"class":77},[71,5249,5250],{"class":81}," Client 内部维护连接池（HTTP keep-alive），复用 TCP\u002FTLS 连接，\n",[71,5252,5253,5255],{"class":73,"line":286},[71,5254,595],{"class":77},[71,5256,5257],{"class":81}," 高频请求下性能差距巨大；还能统一配置超时、默认 header、代理。\n",[71,5259,5260,5262,5265,5267,5269,5271,5274,5276,5279],{"class":73,"line":300},[71,5261,604],{"class":99},[71,5263,5264],{"class":507}," client",[71,5266,610],{"class":99},[71,5268,128],{"class":439},[71,5270,442],{"class":99},[71,5272,5273],{"class":439},"Client",[71,5275,442],{"class":99},[71,5277,5278],{"class":88},"builder",[71,5280,3983],{"class":445},[71,5282,5283,5285,5287,5289,5291,5293,5295,5297,5299,5301,5303],{"class":73,"line":316},[71,5284,1775],{"class":99},[71,5286,3412],{"class":88},[71,5288,475],{"class":445},[71,5290,2864],{"class":439},[71,5292,442],{"class":99},[71,5294,2869],{"class":88},[71,5296,475],{"class":445},[71,5298,3326],{"class":88},[71,5300,1803],{"class":445},[71,5302,918],{"class":77},[71,5304,5305],{"class":81}," 总超时，防止请求永远挂起拖垮服务\n",[71,5307,5308,5310,5313,5315,5317,5319,5321,5323,5325,5327,5330],{"class":73,"line":330},[71,5309,1775],{"class":99},[71,5311,5312],{"class":88},"connect_timeout",[71,5314,475],{"class":445},[71,5316,2864],{"class":439},[71,5318,442],{"class":99},[71,5320,2869],{"class":88},[71,5322,475],{"class":445},[71,5324,4432],{"class":88},[71,5326,1803],{"class":445},[71,5328,5329],{"class":77},"     \u002F\u002F",[71,5331,5332],{"class":81}," 连接建立超时单独设置，更快失败\n",[71,5334,5335,5337,5340,5342,5345,5347,5350],{"class":73,"line":352},[71,5336,1775],{"class":99},[71,5338,5339],{"class":88},"user_agent",[71,5341,475],{"class":445},[71,5343,5344],{"class":92},"\"my-app\u002F1.0\"",[71,5346,681],{"class":445},[71,5348,5349],{"class":77},"                    \u002F\u002F",[71,5351,5352],{"class":81}," 很多 API 拒绝无 UA 的请求\n",[71,5354,5355,5357,5360,5362,5364],{"class":73,"line":601},[71,5356,1775],{"class":99},[71,5358,5359],{"class":88},"build",[71,5361,574],{"class":445},[71,5363,684],{"class":99},[71,5365,687],{"class":445},[71,5367,5368],{"class":73,"line":656},[71,5369,464],{"emptyLinePlaceholder":463},[71,5371,5372,5374],{"class":73,"line":690},[71,5373,595],{"class":77},[71,5375,5376],{"class":81}," POST JSON\n",[71,5378,5379,5381,5384,5386],{"class":73,"line":719},[71,5380,604],{"class":99},[71,5382,5383],{"class":507}," resp",[71,5385,610],{"class":99},[71,5387,5388],{"class":507}," client\n",[71,5390,5391,5393,5396,5398,5401],{"class":73,"line":724},[71,5392,1775],{"class":99},[71,5394,5395],{"class":88},"post",[71,5397,475],{"class":445},[71,5399,5400],{"class":92},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\"",[71,5402,1770],{"class":445},[71,5404,5405,5407,5409,5411,5413,5416,5418,5420,5422,5425,5427,5429,5431,5433,5435,5438,5440,5442,5444,5447,5449],{"class":73,"line":732},[71,5406,1775],{"class":99},[71,5408,5136],{"class":88},[71,5410,475],{"class":445},[71,5412,675],{"class":99},[71,5414,5415],{"class":439},"NewPost",[71,5417,615],{"class":445},[71,5419,4903],{"class":507},[71,5421,511],{"class":99},[71,5423,5424],{"class":92}," \"t\"",[71,5426,626],{"class":99},[71,5428,629],{"class":88},[71,5430,632],{"class":445},[71,5432,4945],{"class":507},[71,5434,511],{"class":99},[71,5436,5437],{"class":92}," \"b\"",[71,5439,626],{"class":99},[71,5441,629],{"class":88},[71,5443,574],{"class":445},[71,5445,5446],{"class":445}," })",[71,5448,551],{"class":77},[71,5450,5451],{"class":81}," 自动序列化 + 设 Content-Type\n",[71,5453,5454,5456,5458],{"class":73,"line":768},[71,5455,1775],{"class":99},[71,5457,4444],{"class":88},[71,5459,3983],{"class":445},[71,5461,5462,5464],{"class":73,"line":773},[71,5463,5042],{"class":99},[71,5465,687],{"class":445},[71,5467,5468,5470,5472,5475,5477,5479,5481,5483,5485,5488],{"class":73,"line":781},[71,5469,693],{"class":88},[71,5471,475],{"class":445},[71,5473,5474],{"class":92},"\"状态: ",[71,5476,741],{"class":445},[71,5478,698],{"class":92},[71,5480,452],{"class":445},[71,5482,5383],{"class":507},[71,5484,626],{"class":99},[71,5486,5487],{"class":88},"status",[71,5489,2019],{"class":445},[71,5491,5492],{"class":73,"line":796},[71,5493,464],{"emptyLinePlaceholder":463},[71,5495,5496,5498],{"class":73,"line":828},[71,5497,595],{"class":77},[71,5499,5500],{"class":81}," GET 带查询参数、自定义 header\n",[71,5502,5503,5505,5507,5509],{"class":73,"line":848},[71,5504,604],{"class":99},[71,5506,5383],{"class":507},[71,5508,610],{"class":99},[71,5510,5388],{"class":507},[71,5512,5513,5515,5517,5519,5521],{"class":73,"line":857},[71,5514,1775],{"class":99},[71,5516,5011],{"class":88},[71,5518,475],{"class":445},[71,5520,5400],{"class":92},[71,5522,1770],{"class":445},[71,5524,5525,5527,5530,5532,5534,5537,5540,5542,5545,5548,5550],{"class":73,"line":2022},[71,5526,1775],{"class":99},[71,5528,5529],{"class":88},"query",[71,5531,475],{"class":445},[71,5533,675],{"class":99},[71,5535,5536],{"class":445},"[(",[71,5538,5539],{"class":92},"\"userId\"",[71,5541,452],{"class":445},[71,5543,5544],{"class":92}," \"1\"",[71,5546,5547],{"class":445},")])",[71,5549,953],{"class":77},[71,5551,5552],{"class":81}," 自动做 URL 编码，别手拼字符串\n",[71,5554,5555,5557,5560,5562,5565,5567,5570],{"class":73,"line":2027},[71,5556,1775],{"class":99},[71,5558,5559],{"class":88},"header",[71,5561,475],{"class":445},[71,5563,5564],{"class":92},"\"Authorization\"",[71,5566,452],{"class":445},[71,5568,5569],{"class":92}," \"Bearer token123\"",[71,5571,1770],{"class":445},[71,5573,5574,5576,5578],{"class":73,"line":2035},[71,5575,1775],{"class":99},[71,5577,4444],{"class":88},[71,5579,3983],{"class":445},[71,5581,5582,5584],{"class":73,"line":2068},[71,5583,5042],{"class":99},[71,5585,687],{"class":445},[71,5587,5588],{"class":73,"line":2095},[71,5589,464],{"emptyLinePlaceholder":463},[71,5591,5592,5594],{"class":73,"line":2131},[71,5593,595],{"class":77},[71,5595,5596],{"class":81}," error_for_status：把 4xx\u002F5xx 变成 Err，否则 reqwest 认为 HTTP 层成功就是成功\n",[71,5598,5599,5601,5604,5606,5608,5610,5613,5615,5617,5619,5621,5623,5625,5628,5630,5633,5635,5637,5639],{"class":73,"line":2136},[71,5600,604],{"class":99},[71,5602,5603],{"class":507}," posts",[71,5605,511],{"class":99},[71,5607,966],{"class":439},[71,5609,542],{"class":445},[71,5611,5612],{"class":507},"serde_json",[71,5614,442],{"class":99},[71,5616,1143],{"class":439},[71,5618,1730],{"class":445},[71,5620,610],{"class":99},[71,5622,5383],{"class":507},[71,5624,626],{"class":99},[71,5626,5627],{"class":88},"error_for_status",[71,5629,574],{"class":445},[71,5631,5632],{"class":99},"?.",[71,5634,5136],{"class":88},[71,5636,574],{"class":445},[71,5638,3736],{"class":99},[71,5640,687],{"class":445},[71,5642,5643,5645,5647,5650,5652,5655,5657,5659,5661,5663],{"class":73,"line":2143},[71,5644,693],{"class":88},[71,5646,475],{"class":445},[71,5648,5649],{"class":92},"\"拿到 ",[71,5651,741],{"class":445},[71,5653,5654],{"class":92}," 条\"",[71,5656,452],{"class":445},[71,5658,5603],{"class":507},[71,5660,626],{"class":99},[71,5662,2016],{"class":88},[71,5664,2019],{"class":445},[71,5666,5667,5669],{"class":73,"line":4626},[71,5668,851],{"class":439},[71,5670,854],{"class":445},[71,5672,5673],{"class":73,"line":4649},[71,5674,559],{"class":445},[14,5676,5677,5680],{},[18,5678,5679],{},"为什么一定要设超时？"," 不设超时的 HTTP 调用是对下游故障敞开的：对方服务挂起时你的每个请求都永远占着一个任务和连接，最终耗尽资源级联雪崩。所有出网调用都必须有超时，这是服务端开发的第一戒律。",[57,5682,5684],{"id":5683},"_44-大文件流式下载","4.4 大文件流式下载",[62,5686,5688],{"className":427,"code":5687,"language":429,"meta":67,"style":67},"use futures_util::StreamExt;\nuse tokio::io::AsyncWriteExt;\n\nasync fn download(client: &reqwest::Client, url: &str, path: &str) -> reqwest::Result\u003C()> {\n    let mut resp = client.get(url).send().await?.error_for_status()?;\n    let mut file = tokio::fs::File::create(path).await.unwrap();\n\n    \u002F\u002F bytes_stream() 边下边写，内存占用恒定。如果把整个 body 读进内存，2GB 文件就占 2GB 内存\n    while let Some(chunk) = resp.bytes_stream().next().await {\n        file.write_all(&chunk?).await.unwrap();\n    }\n    Ok(())\n}\n",[52,5689,5690,5704,5720,5724,5785,5824,5861,5865,5872,5909,5934,5938,5944],{"__ignoreMap":67},[71,5691,5692,5694,5697,5699,5702],{"class":73,"line":74},[71,5693,436],{"class":99},[71,5695,5696],{"class":439}," futures_util",[71,5698,442],{"class":99},[71,5700,5701],{"class":439},"StreamExt",[71,5703,687],{"class":445},[71,5705,5706,5708,5710,5712,5714,5716,5718],{"class":73,"line":85},[71,5707,436],{"class":99},[71,5709,113],{"class":439},[71,5711,442],{"class":99},[71,5713,3648],{"class":439},[71,5715,442],{"class":99},[71,5717,3655],{"class":439},[71,5719,687],{"class":445},[71,5721,5722],{"class":73,"line":106},[71,5723,464],{"emptyLinePlaceholder":463},[71,5725,5726,5728,5730,5733,5735,5738,5740,5742,5745,5747,5749,5751,5754,5756,5758,5760,5762,5765,5767,5769,5771,5773,5775,5777,5779,5781,5783],{"class":73,"line":121},[71,5727,2790],{"class":99},[71,5729,2793],{"class":99},[71,5731,5732],{"class":88}," download",[71,5734,475],{"class":445},[71,5736,5737],{"class":507},"client",[71,5739,511],{"class":99},[71,5741,1714],{"class":99},[71,5743,5744],{"class":439},"reqwest",[71,5746,442],{"class":99},[71,5748,5273],{"class":439},[71,5750,452],{"class":445},[71,5752,5753],{"class":507}," url",[71,5755,511],{"class":99},[71,5757,1714],{"class":99},[71,5759,1717],{"class":439},[71,5761,452],{"class":445},[71,5763,5764],{"class":507}," path",[71,5766,511],{"class":99},[71,5768,1714],{"class":99},[71,5770,1717],{"class":439},[71,5772,681],{"class":445},[71,5774,577],{"class":99},[71,5776,128],{"class":439},[71,5778,442],{"class":99},[71,5780,585],{"class":439},[71,5782,588],{"class":445},[71,5784,502],{"class":445},[71,5786,5787,5789,5791,5793,5795,5797,5799,5801,5803,5806,5808,5810,5812,5814,5816,5818,5820,5822],{"class":73,"line":259},[71,5788,604],{"class":99},[71,5790,3780],{"class":99},[71,5792,5383],{"class":507},[71,5794,610],{"class":99},[71,5796,5264],{"class":507},[71,5798,626],{"class":99},[71,5800,5011],{"class":88},[71,5802,475],{"class":445},[71,5804,5805],{"class":507},"url",[71,5807,681],{"class":445},[71,5809,626],{"class":99},[71,5811,4444],{"class":88},[71,5813,574],{"class":445},[71,5815,3900],{"class":99},[71,5817,5627],{"class":88},[71,5819,574],{"class":445},[71,5821,684],{"class":99},[71,5823,687],{"class":445},[71,5825,5826,5828,5830,5833,5835,5837,5839,5841,5843,5845,5847,5849,5851,5853,5855,5857,5859],{"class":73,"line":273},[71,5827,604],{"class":99},[71,5829,3780],{"class":99},[71,5831,5832],{"class":507}," file",[71,5834,610],{"class":99},[71,5836,113],{"class":439},[71,5838,442],{"class":99},[71,5840,1684],{"class":439},[71,5842,442],{"class":99},[71,5844,3792],{"class":439},[71,5846,442],{"class":99},[71,5848,3797],{"class":88},[71,5850,475],{"class":445},[71,5852,1709],{"class":507},[71,5854,681],{"class":445},[71,5856,2915],{"class":99},[71,5858,1496],{"class":88},[71,5860,1499],{"class":445},[71,5862,5863],{"class":73,"line":286},[71,5864,464],{"emptyLinePlaceholder":463},[71,5866,5867,5869],{"class":73,"line":300},[71,5868,595],{"class":77},[71,5870,5871],{"class":81}," bytes_stream() 边下边写，内存占用恒定。如果把整个 body 读进内存，2GB 文件就占 2GB 内存\n",[71,5873,5874,5876,5878,5880,5882,5885,5887,5889,5891,5893,5896,5898,5900,5903,5905,5907],{"class":73,"line":316},[71,5875,4485],{"class":99},[71,5877,1298],{"class":99},[71,5879,1301],{"class":439},[71,5881,475],{"class":445},[71,5883,5884],{"class":507},"chunk",[71,5886,681],{"class":445},[71,5888,610],{"class":99},[71,5890,5383],{"class":507},[71,5892,626],{"class":99},[71,5894,5895],{"class":88},"bytes_stream",[71,5897,574],{"class":445},[71,5899,626],{"class":99},[71,5901,5902],{"class":88},"next",[71,5904,574],{"class":445},[71,5906,2878],{"class":99},[71,5908,502],{"class":445},[71,5910,5911,5914,5916,5918,5920,5922,5924,5926,5928,5930,5932],{"class":73,"line":330},[71,5912,5913],{"class":507},"        file",[71,5915,626],{"class":99},[71,5917,3818],{"class":88},[71,5919,475],{"class":445},[71,5921,675],{"class":99},[71,5923,5884],{"class":507},[71,5925,684],{"class":99},[71,5927,681],{"class":445},[71,5929,2915],{"class":99},[71,5931,1496],{"class":88},[71,5933,1499],{"class":445},[71,5935,5936],{"class":73,"line":352},[71,5937,1935],{"class":445},[71,5939,5940,5942],{"class":73,"line":601},[71,5941,851],{"class":439},[71,5943,854],{"class":445},[71,5945,5946],{"class":73,"line":656},[71,5947,559],{"class":445},[57,5949,5951],{"id":5950},"_45-表单与-multipart-上传","4.5 表单与 multipart 上传",[62,5953,5955],{"className":427,"code":5954,"language":429,"meta":67,"style":67},"async fn forms(client: &reqwest::Client) -> reqwest::Result\u003C()> {\n    \u002F\u002F application\u002Fx-www-form-urlencoded 表单\n    client.post(\"https:\u002F\u002Fhttpbin.org\u002Fpost\")\n        .form(&[(\"username\", \"admin\"), (\"password\", \"123456\")])\n        .send().await?;\n\n    \u002F\u002F multipart 文件上传\n    let part = reqwest::multipart::Part::bytes(std::fs::read(\"a.txt\").unwrap())\n        .file_name(\"a.txt\");\n    let form = reqwest::multipart::Form::new()\n        .text(\"desc\", \"一个文件\")\n        .part(\"file\", part);\n    client.post(\"https:\u002F\u002Fhttpbin.org\u002Fpost\").multipart(form).send().await?;\n    Ok(())\n}\n",[52,5956,5957,5994,6001,6017,6053,6065,6069,6076,6127,6140,6166,6184,6202,6236,6242],{"__ignoreMap":67},[71,5958,5959,5961,5963,5966,5968,5970,5972,5974,5976,5978,5980,5982,5984,5986,5988,5990,5992],{"class":73,"line":74},[71,5960,2790],{"class":99},[71,5962,2793],{"class":99},[71,5964,5965],{"class":88}," forms",[71,5967,475],{"class":445},[71,5969,5737],{"class":507},[71,5971,511],{"class":99},[71,5973,1714],{"class":99},[71,5975,5744],{"class":439},[71,5977,442],{"class":99},[71,5979,5273],{"class":439},[71,5981,681],{"class":445},[71,5983,577],{"class":99},[71,5985,128],{"class":439},[71,5987,442],{"class":99},[71,5989,585],{"class":439},[71,5991,588],{"class":445},[71,5993,502],{"class":445},[71,5995,5996,5998],{"class":73,"line":85},[71,5997,595],{"class":77},[71,5999,6000],{"class":81}," application\u002Fx-www-form-urlencoded 表单\n",[71,6002,6003,6006,6008,6010,6012,6015],{"class":73,"line":106},[71,6004,6005],{"class":507},"    client",[71,6007,626],{"class":99},[71,6009,5395],{"class":88},[71,6011,475],{"class":445},[71,6013,6014],{"class":92},"\"https:\u002F\u002Fhttpbin.org\u002Fpost\"",[71,6016,1770],{"class":445},[71,6018,6019,6021,6024,6026,6028,6030,6033,6035,6038,6040,6042,6045,6047,6050],{"class":73,"line":121},[71,6020,1775],{"class":99},[71,6022,6023],{"class":88},"form",[71,6025,475],{"class":445},[71,6027,675],{"class":99},[71,6029,5536],{"class":445},[71,6031,6032],{"class":92},"\"username\"",[71,6034,452],{"class":445},[71,6036,6037],{"class":92}," \"admin\"",[71,6039,2398],{"class":445},[71,6041,3162],{"class":445},[71,6043,6044],{"class":92},"\"password\"",[71,6046,452],{"class":445},[71,6048,6049],{"class":92}," \"123456\"",[71,6051,6052],{"class":445},")])\n",[71,6054,6055,6057,6059,6061,6063],{"class":73,"line":259},[71,6056,1775],{"class":99},[71,6058,4444],{"class":88},[71,6060,574],{"class":445},[71,6062,3736],{"class":99},[71,6064,687],{"class":445},[71,6066,6067],{"class":73,"line":273},[71,6068,464],{"emptyLinePlaceholder":463},[71,6070,6071,6073],{"class":73,"line":286},[71,6072,595],{"class":77},[71,6074,6075],{"class":81}," multipart 文件上传\n",[71,6077,6078,6080,6083,6085,6087,6089,6092,6094,6097,6099,6102,6104,6106,6108,6110,6112,6115,6117,6119,6121,6123,6125],{"class":73,"line":300},[71,6079,604],{"class":99},[71,6081,6082],{"class":507}," part",[71,6084,610],{"class":99},[71,6086,128],{"class":439},[71,6088,442],{"class":99},[71,6090,6091],{"class":439},"multipart",[71,6093,442],{"class":99},[71,6095,6096],{"class":439},"Part",[71,6098,442],{"class":99},[71,6100,6101],{"class":88},"bytes",[71,6103,475],{"class":445},[71,6105,2855],{"class":439},[71,6107,442],{"class":99},[71,6109,1684],{"class":439},[71,6111,442],{"class":99},[71,6113,6114],{"class":88},"read",[71,6116,475],{"class":445},[71,6118,3726],{"class":92},[71,6120,681],{"class":445},[71,6122,626],{"class":99},[71,6124,1496],{"class":88},[71,6126,2500],{"class":445},[71,6128,6129,6131,6134,6136,6138],{"class":73,"line":316},[71,6130,1775],{"class":99},[71,6132,6133],{"class":88},"file_name",[71,6135,475],{"class":445},[71,6137,3726],{"class":92},[71,6139,765],{"class":445},[71,6141,6142,6144,6147,6149,6151,6153,6155,6157,6160,6162,6164],{"class":73,"line":330},[71,6143,604],{"class":99},[71,6145,6146],{"class":507}," form",[71,6148,610],{"class":99},[71,6150,128],{"class":439},[71,6152,442],{"class":99},[71,6154,6091],{"class":439},[71,6156,442],{"class":99},[71,6158,6159],{"class":439},"Form",[71,6161,442],{"class":99},[71,6163,3876],{"class":88},[71,6165,3983],{"class":445},[71,6167,6168,6170,6172,6174,6177,6179,6182],{"class":73,"line":352},[71,6169,1775],{"class":99},[71,6171,5030],{"class":88},[71,6173,475],{"class":445},[71,6175,6176],{"class":92},"\"desc\"",[71,6178,452],{"class":445},[71,6180,6181],{"class":92}," \"一个文件\"",[71,6183,1770],{"class":445},[71,6185,6186,6188,6191,6193,6196,6198,6200],{"class":73,"line":601},[71,6187,1775],{"class":99},[71,6189,6190],{"class":88},"part",[71,6192,475],{"class":445},[71,6194,6195],{"class":92},"\"file\"",[71,6197,452],{"class":445},[71,6199,6082],{"class":507},[71,6201,765],{"class":445},[71,6203,6204,6206,6208,6210,6212,6214,6216,6218,6220,6222,6224,6226,6228,6230,6232,6234],{"class":73,"line":656},[71,6205,6005],{"class":507},[71,6207,626],{"class":99},[71,6209,5395],{"class":88},[71,6211,475],{"class":445},[71,6213,6014],{"class":92},[71,6215,681],{"class":445},[71,6217,626],{"class":99},[71,6219,6091],{"class":88},[71,6221,475],{"class":445},[71,6223,6023],{"class":507},[71,6225,681],{"class":445},[71,6227,626],{"class":99},[71,6229,4444],{"class":88},[71,6231,574],{"class":445},[71,6233,3736],{"class":99},[71,6235,687],{"class":445},[71,6237,6238,6240],{"class":73,"line":690},[71,6239,851],{"class":439},[71,6241,854],{"class":445},[71,6243,6244],{"class":73,"line":719},[71,6245,559],{"class":445},[23,6247],{},[26,6249,6251],{"id":6250},"第-5-章-axumweb-服务端框架","第 5 章 axum：Web 服务端框架",[57,6253,6255],{"id":6254},"_51-它解决什么问题","5.1 它解决什么问题",[14,6257,6258,6259,6262],{},"axum 是 tokio 团队官方生态的 Web 框架，基于 tower 抽象，与 tokio\u002Fhyper 无缝配合。特点是",[18,6260,6261],{},"没有自己的宏魔法，路由和提取器全是类型系统","——编译器会拦住绝大多数错误。",[62,6264,6266],{"className":143,"code":6265,"language":145,"meta":67,"style":67},"[dependencies]\naxum = \"0.8\"\ntokio = { version = \"1\", features = [\"full\"] }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n",[52,6267,6268,6272,6277,6281,6285],{"__ignoreMap":67},[71,6269,6270],{"class":73,"line":74},[71,6271,152],{},[71,6273,6274],{"class":73,"line":85},[71,6275,6276],{},"axum = \"0.8\"\n",[71,6278,6279],{"class":73,"line":106},[71,6280,162],{},[71,6282,6283],{"class":73,"line":121},[71,6284,157],{},[71,6286,6287],{"class":73,"line":259},[71,6288,424],{},[11,6290,6291],{},[14,6292,6293,6294,6297,6298,6301],{},"注意：axum 0.8 起路径参数语法是 ",[52,6295,6296],{},"{id}","（旧版 ",[52,6299,6300],{},":id"," 已废弃）。",[57,6303,6305],{"id":6304},"_52-最小可运行服务","5.2 最小可运行服务",[62,6307,6309],{"className":427,"code":6308,"language":429,"meta":67,"style":67},"use axum::{routing::{get, post}, Router, Json};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Serialize)]\nstruct Msg { message: String }\n\n#[derive(Deserialize)]\nstruct CreateUser { name: String }\n\nasync fn hello() -> &'static str { \"Hello, world\" }       \u002F\u002F 返回 &str 自动成为 text\u002Fplain\nasync fn hello_json() -> Json\u003CMsg> {                       \u002F\u002F 返回 Json\u003CT> 自动序列化 + 设 Content-Type\n    Json(Msg { message: \"你好\".into() })\n}\nasync fn create_user(Json(payload): Json\u003CCreateUser>) -> Json\u003CMsg> {\n    \u002F\u002F Json\u003CT> 既是提取器（请求）也是响应器：靠的是函数参数的「提取器模式」\n    Json(Msg { message: format!(\"创建用户 {}\", payload.name) })\n}\n\n#[tokio::main]\nasync fn main() {\n    let app = Router::new()\n        .route(\"\u002F\", get(hello))\n        .route(\"\u002Fjson\", get(hello_json))\n        .route(\"\u002Fusers\", post(create_user));\n\n    \u002F\u002F 0.8 写法：TcpListener + serve\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    println!(\"监听 http:\u002F\u002Flocalhost:3000\");\n    axum::serve(listener, app).await.unwrap();\n}\n",[52,6310,6311,6348,6366,6370,6382,6400,6404,6416,6433,6437,6472,6501,6527,6531,6576,6583,6620,6624,6628,6640,6652,6669,6694,6716,6738,6742,6749,6788,6799,6826],{"__ignoreMap":67},[71,6312,6313,6315,6318,6320,6322,6325,6327,6329,6331,6333,6335,6338,6341,6343,6346],{"class":73,"line":74},[71,6314,436],{"class":99},[71,6316,6317],{"class":439}," axum",[71,6319,442],{"class":99},[71,6321,446],{"class":445},[71,6323,6324],{"class":439},"routing",[71,6326,442],{"class":99},[71,6328,446],{"class":445},[71,6330,5011],{"class":472},[71,6332,452],{"class":445},[71,6334,5106],{"class":472},[71,6336,6337],{"class":445},"},",[71,6339,6340],{"class":439}," Router",[71,6342,452],{"class":445},[71,6344,6345],{"class":439}," Json",[71,6347,458],{"class":445},[71,6349,6350,6352,6354,6356,6358,6360,6362,6364],{"class":73,"line":85},[71,6351,436],{"class":99},[71,6353,96],{"class":439},[71,6355,442],{"class":99},[71,6357,446],{"class":445},[71,6359,449],{"class":439},[71,6361,452],{"class":445},[71,6363,455],{"class":439},[71,6365,458],{"class":445},[71,6367,6368],{"class":73,"line":106},[71,6369,464],{"emptyLinePlaceholder":463},[71,6371,6372,6374,6376,6378,6380],{"class":73,"line":121},[71,6373,469],{"class":445},[71,6375,176],{"class":472},[71,6377,475],{"class":445},[71,6379,478],{"class":439},[71,6381,491],{"class":445},[71,6383,6384,6386,6389,6391,6394,6396,6398],{"class":73,"line":259},[71,6385,496],{"class":99},[71,6387,6388],{"class":439}," Msg",[71,6390,615],{"class":445},[71,6392,6393],{"class":507}," message",[71,6395,511],{"class":99},[71,6397,514],{"class":439},[71,6399,1180],{"class":445},[71,6401,6402],{"class":73,"line":273},[71,6403,464],{"emptyLinePlaceholder":463},[71,6405,6406,6408,6410,6412,6414],{"class":73,"line":286},[71,6407,469],{"class":445},[71,6409,176],{"class":472},[71,6411,475],{"class":445},[71,6413,449],{"class":439},[71,6415,491],{"class":445},[71,6417,6418,6420,6423,6425,6427,6429,6431],{"class":73,"line":300},[71,6419,496],{"class":99},[71,6421,6422],{"class":439}," CreateUser",[71,6424,615],{"class":445},[71,6426,618],{"class":507},[71,6428,511],{"class":99},[71,6430,514],{"class":439},[71,6432,1180],{"class":445},[71,6434,6435],{"class":73,"line":316},[71,6436,464],{"emptyLinePlaceholder":463},[71,6438,6439,6441,6443,6446,6448,6450,6452,6454,6456,6458,6460,6463,6466,6469],{"class":73,"line":330},[71,6440,2790],{"class":99},[71,6442,2793],{"class":99},[71,6444,6445],{"class":88}," hello",[71,6447,574],{"class":445},[71,6449,577],{"class":99},[71,6451,1714],{"class":99},[71,6453,3440],{"class":445},[71,6455,3443],{"class":439},[71,6457,3446],{"class":439},[71,6459,615],{"class":445},[71,6461,6462],{"class":92}," \"Hello, world\"",[71,6464,6465],{"class":445}," }",[71,6467,6468],{"class":77},"       \u002F\u002F",[71,6470,6471],{"class":81}," 返回 &str 自动成为 text\u002Fplain\n",[71,6473,6474,6476,6478,6481,6483,6485,6487,6489,6492,6494,6496,6498],{"class":73,"line":352},[71,6475,2790],{"class":99},[71,6477,2793],{"class":99},[71,6479,6480],{"class":88}," hello_json",[71,6482,574],{"class":445},[71,6484,577],{"class":99},[71,6486,6345],{"class":439},[71,6488,542],{"class":445},[71,6490,6491],{"class":439},"Msg",[71,6493,1730],{"class":445},[71,6495,615],{"class":445},[71,6497,2357],{"class":77},[71,6499,6500],{"class":81}," 返回 Json\u003CT> 自动序列化 + 设 Content-Type\n",[71,6502,6503,6506,6508,6510,6512,6514,6516,6518,6520,6522,6524],{"class":73,"line":601},[71,6504,6505],{"class":88},"    Json",[71,6507,475],{"class":445},[71,6509,6491],{"class":439},[71,6511,615],{"class":445},[71,6513,6393],{"class":507},[71,6515,511],{"class":99},[71,6517,3731],{"class":92},[71,6519,626],{"class":99},[71,6521,629],{"class":88},[71,6523,574],{"class":445},[71,6525,6526],{"class":445}," })\n",[71,6528,6529],{"class":73,"line":656},[71,6530,559],{"class":445},[71,6532,6533,6535,6537,6540,6542,6545,6547,6550,6552,6554,6556,6558,6561,6564,6566,6568,6570,6572,6574],{"class":73,"line":690},[71,6534,2790],{"class":99},[71,6536,2793],{"class":99},[71,6538,6539],{"class":88}," create_user",[71,6541,475],{"class":445},[71,6543,6544],{"class":88},"Json",[71,6546,475],{"class":445},[71,6548,6549],{"class":507},"payload",[71,6551,681],{"class":445},[71,6553,511],{"class":99},[71,6555,6345],{"class":439},[71,6557,542],{"class":445},[71,6559,6560],{"class":439},"CreateUser",[71,6562,6563],{"class":445},">)",[71,6565,577],{"class":99},[71,6567,6345],{"class":439},[71,6569,542],{"class":445},[71,6571,6491],{"class":439},[71,6573,1730],{"class":445},[71,6575,502],{"class":445},[71,6577,6578,6580],{"class":73,"line":719},[71,6579,595],{"class":77},[71,6581,6582],{"class":81}," Json\u003CT> 既是提取器（请求）也是响应器：靠的是函数参数的「提取器模式」\n",[71,6584,6585,6587,6589,6591,6593,6595,6597,6599,6601,6604,6606,6608,6610,6612,6614,6616,6618],{"class":73,"line":724},[71,6586,6505],{"class":88},[71,6588,475],{"class":445},[71,6590,6491],{"class":439},[71,6592,615],{"class":445},[71,6594,6393],{"class":507},[71,6596,511],{"class":99},[71,6598,1786],{"class":88},[71,6600,475],{"class":445},[71,6602,6603],{"class":92},"\"创建用户 ",[71,6605,741],{"class":445},[71,6607,698],{"class":92},[71,6609,452],{"class":445},[71,6611,1387],{"class":507},[71,6613,626],{"class":99},[71,6615,1306],{"class":472},[71,6617,681],{"class":445},[71,6619,6526],{"class":445},[71,6621,6622],{"class":73,"line":732},[71,6623,559],{"class":445},[71,6625,6626],{"class":73,"line":768},[71,6627,464],{"emptyLinePlaceholder":463},[71,6629,6630,6632,6634,6636,6638],{"class":73,"line":773},[71,6631,469],{"class":445},[71,6633,2777],{"class":472},[71,6635,442],{"class":99},[71,6637,2782],{"class":472},[71,6639,2785],{"class":445},[71,6641,6642,6644,6646,6648,6650],{"class":73,"line":781},[71,6643,2790],{"class":99},[71,6645,2793],{"class":99},[71,6647,571],{"class":88},[71,6649,574],{"class":445},[71,6651,502],{"class":445},[71,6653,6654,6656,6659,6661,6663,6665,6667],{"class":73,"line":796},[71,6655,604],{"class":99},[71,6657,6658],{"class":507}," app",[71,6660,610],{"class":99},[71,6662,6340],{"class":439},[71,6664,442],{"class":99},[71,6666,3876],{"class":88},[71,6668,3983],{"class":445},[71,6670,6671,6673,6676,6678,6681,6683,6686,6688,6691],{"class":73,"line":828},[71,6672,1775],{"class":99},[71,6674,6675],{"class":88},"route",[71,6677,475],{"class":445},[71,6679,6680],{"class":92},"\"\u002F\"",[71,6682,452],{"class":445},[71,6684,6685],{"class":88}," get",[71,6687,475],{"class":445},[71,6689,6690],{"class":507},"hello",[71,6692,6693],{"class":445},"))\n",[71,6695,6696,6698,6700,6702,6705,6707,6709,6711,6714],{"class":73,"line":848},[71,6697,1775],{"class":99},[71,6699,6675],{"class":88},[71,6701,475],{"class":445},[71,6703,6704],{"class":92},"\"\u002Fjson\"",[71,6706,452],{"class":445},[71,6708,6685],{"class":88},[71,6710,475],{"class":445},[71,6712,6713],{"class":507},"hello_json",[71,6715,6693],{"class":445},[71,6717,6718,6720,6722,6724,6727,6729,6731,6733,6736],{"class":73,"line":857},[71,6719,1775],{"class":99},[71,6721,6675],{"class":88},[71,6723,475],{"class":445},[71,6725,6726],{"class":92},"\"\u002Fusers\"",[71,6728,452],{"class":445},[71,6730,5106],{"class":88},[71,6732,475],{"class":445},[71,6734,6735],{"class":507},"create_user",[71,6737,4146],{"class":445},[71,6739,6740],{"class":73,"line":2022},[71,6741,464],{"emptyLinePlaceholder":463},[71,6743,6744,6746],{"class":73,"line":2027},[71,6745,595],{"class":77},[71,6747,6748],{"class":81}," 0.8 写法：TcpListener + serve\n",[71,6750,6751,6753,6756,6758,6760,6762,6765,6767,6770,6772,6775,6777,6780,6782,6784,6786],{"class":73,"line":2035},[71,6752,604],{"class":99},[71,6754,6755],{"class":507}," listener",[71,6757,610],{"class":99},[71,6759,113],{"class":439},[71,6761,442],{"class":99},[71,6763,6764],{"class":439},"net",[71,6766,442],{"class":99},[71,6768,6769],{"class":439},"TcpListener",[71,6771,442],{"class":99},[71,6773,6774],{"class":88},"bind",[71,6776,475],{"class":445},[71,6778,6779],{"class":92},"\"0.0.0.0:3000\"",[71,6781,681],{"class":445},[71,6783,2915],{"class":99},[71,6785,1496],{"class":88},[71,6787,1499],{"class":445},[71,6789,6790,6792,6794,6797],{"class":73,"line":2068},[71,6791,693],{"class":88},[71,6793,475],{"class":445},[71,6795,6796],{"class":92},"\"监听 http:\u002F\u002Flocalhost:3000\"",[71,6798,765],{"class":445},[71,6800,6801,6804,6806,6809,6811,6814,6816,6818,6820,6822,6824],{"class":73,"line":2095},[71,6802,6803],{"class":439},"    axum",[71,6805,442],{"class":99},[71,6807,6808],{"class":88},"serve",[71,6810,475],{"class":445},[71,6812,6813],{"class":507},"listener",[71,6815,452],{"class":445},[71,6817,6658],{"class":507},[71,6819,681],{"class":445},[71,6821,2915],{"class":99},[71,6823,1496],{"class":88},[71,6825,1499],{"class":445},[71,6827,6828],{"class":73,"line":2131},[71,6829,559],{"class":445},[14,6831,6832,6835,6836,6839],{},[18,6833,6834],{},"为什么 handler 是普通 async fn 而不是 trait？"," axum 的提取器模式（handler 参数实现 ",[52,6837,6838],{},"FromRequest"," 即可）让 handler 签名本身就是 API 文档：参数声明了需要什么，返回类型声明了产出什么，全部编译期检查。请求缺字段、类型不符，根本到不了你的业务代码。",[57,6841,6843],{"id":6842},"_53-路径参数查询参数header共享状态","5.3 路径参数、查询参数、Header、共享状态",[62,6845,6847],{"className":427,"code":6846,"language":429,"meta":67,"style":67},"use axum::{\n    extract::{Path, Query, State},\n    http::{HeaderMap, StatusCode},\n    Json, Router, routing::get,\n};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, sync::{Arc, atomic::{AtomicU64, Ordering}}};\n\n\u002F\u002F 共享状态：Arc 包一层。原子计数器之外的可变数据用 Mutex\u002FRwLock\n#[derive(Clone)]\nstruct AppState {\n    visit_count: Arc\u003CAtomicU64>,\n    app_name: String,\n}\n\n#[derive(Deserialize)]\nstruct PageQuery { page: Option\u003Cu32>, size: Option\u003Cu32> }\n\nasync fn get_user(Path(id): Path\u003Cu64>) -> String { format!(\"用户 {id}\") }\n\nasync fn list_users(Query(q): Query\u003CPageQuery>) -> String {\n    format!(\"第 {} 页，每页 {} 条\", q.page.unwrap_or(1), q.size.unwrap_or(20))\n}\n\nasync fn visit(State(state): State\u003CAppState>, headers: HeaderMap) -> String {\n    let n = state.visit_count.fetch_add(1, Ordering::Relaxed) + 1;\n    let ua = headers.get(\"user-agent\")\n        .and_then(|v| v.to_str().ok()).unwrap_or(\"unknown\");\n    format!(\"[{}] 第 {n} 次访问，UA: {ua}\", state.app_name)\n}\n\n\u002F\u002F 返回 (StatusCode, Json\u003CT>) 自定义状态码；impl IntoResponse 可以返回任意可转响应的类型\nasync fn maybe_found(State(s): State\u003CAppState>) -> Result\u003CJson\u003Cserde_json::Value>, StatusCode> {\n    if s.app_name.is_empty() {\n        Err(StatusCode::NOT_FOUND) \u002F\u002F 返回 Err(StatusCode) 直接成为对应状态码的空响应\n    } else {\n        Ok(Json(serde_json::json!({\"ok\": true})))\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    let state = AppState {\n        visit_count: Arc::new(AtomicU64::new(0)),\n        app_name: \"demo\".into(),\n    };\n\n    let app = Router::new()\n        .route(\"\u002Fusers\u002F{id}\", get(get_user))          \u002F\u002F {id} 与 Path\u003Cu64> 对应，类型不符自动 400\n        .route(\"\u002Fusers\", get(list_users))\n        .route(\"\u002Fvisit\", get(visit))\n        .route(\"\u002Ffound\", get(maybe_found))\n        .with_state(state);                            \u002F\u002F 注入状态，handler 用 State 提取\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    axum::serve(listener, app).await.unwrap();\n}\n",[52,6848,6849,6860,6885,6904,6923,6927,6945,6992,6996,7003,7016,7025,7040,7051,7055,7059,7071,7109,7113,7168,7172,7210,7268,7272,7276,7324,7367,7389,7432,7475,7479,7483,7490,7543,7562,7581,7591,7620,7624,7628,7632,7644,7657,7670,7699,7717,7723,7728,7745,7781,7803,7826,7849,7869,7874,7909,7934],{"__ignoreMap":67},[71,6850,6851,6853,6855,6857],{"class":73,"line":74},[71,6852,436],{"class":99},[71,6854,6317],{"class":439},[71,6856,442],{"class":99},[71,6858,6859],{"class":445},"{\n",[71,6861,6862,6865,6867,6869,6872,6874,6877,6879,6882],{"class":73,"line":85},[71,6863,6864],{"class":439},"    extract",[71,6866,442],{"class":99},[71,6868,446],{"class":445},[71,6870,6871],{"class":439},"Path",[71,6873,452],{"class":445},[71,6875,6876],{"class":439}," Query",[71,6878,452],{"class":445},[71,6880,6881],{"class":439}," State",[71,6883,6884],{"class":445},"},\n",[71,6886,6887,6890,6892,6894,6897,6899,6902],{"class":73,"line":106},[71,6888,6889],{"class":439},"    http",[71,6891,442],{"class":99},[71,6893,446],{"class":445},[71,6895,6896],{"class":439},"HeaderMap",[71,6898,452],{"class":445},[71,6900,6901],{"class":439}," StatusCode",[71,6903,6884],{"class":445},[71,6905,6906,6908,6910,6912,6914,6917,6919,6921],{"class":73,"line":121},[71,6907,6505],{"class":439},[71,6909,452],{"class":445},[71,6911,6340],{"class":439},[71,6913,452],{"class":445},[71,6915,6916],{"class":439}," routing",[71,6918,442],{"class":99},[71,6920,5011],{"class":472},[71,6922,517],{"class":445},[71,6924,6925],{"class":73,"line":259},[71,6926,458],{"class":445},[71,6928,6929,6931,6933,6935,6937,6939,6941,6943],{"class":73,"line":273},[71,6930,436],{"class":99},[71,6932,96],{"class":439},[71,6934,442],{"class":99},[71,6936,446],{"class":445},[71,6938,449],{"class":439},[71,6940,452],{"class":445},[71,6942,455],{"class":439},[71,6944,458],{"class":445},[71,6946,6947,6949,6951,6953,6955,6957,6959,6961,6963,6966,6968,6970,6972,6974,6977,6979,6981,6984,6986,6989],{"class":73,"line":286},[71,6948,436],{"class":99},[71,6950,1120],{"class":439},[71,6952,442],{"class":99},[71,6954,446],{"class":445},[71,6956,1125],{"class":439},[71,6958,442],{"class":99},[71,6960,1130],{"class":439},[71,6962,452],{"class":445},[71,6964,6965],{"class":439}," sync",[71,6967,442],{"class":99},[71,6969,446],{"class":445},[71,6971,4032],{"class":439},[71,6973,452],{"class":445},[71,6975,6976],{"class":439}," atomic",[71,6978,442],{"class":99},[71,6980,446],{"class":445},[71,6982,6983],{"class":439},"AtomicU64",[71,6985,452],{"class":445},[71,6987,6988],{"class":439}," Ordering",[71,6990,6991],{"class":445},"}}};\n",[71,6993,6994],{"class":73,"line":300},[71,6995,464],{"emptyLinePlaceholder":463},[71,6997,6998,7000],{"class":73,"line":316},[71,6999,998],{"class":77},[71,7001,7002],{"class":81}," 共享状态：Arc 包一层。原子计数器之外的可变数据用 Mutex\u002FRwLock\n",[71,7004,7005,7007,7009,7011,7014],{"class":73,"line":330},[71,7006,469],{"class":445},[71,7008,176],{"class":472},[71,7010,475],{"class":445},[71,7012,7013],{"class":439},"Clone",[71,7015,491],{"class":445},[71,7017,7018,7020,7023],{"class":73,"line":352},[71,7019,496],{"class":99},[71,7021,7022],{"class":439}," AppState",[71,7024,502],{"class":445},[71,7026,7027,7030,7032,7034,7036,7038],{"class":73,"line":601},[71,7028,7029],{"class":507},"    visit_count",[71,7031,511],{"class":99},[71,7033,4125],{"class":439},[71,7035,542],{"class":445},[71,7037,6983],{"class":439},[71,7039,973],{"class":445},[71,7041,7042,7045,7047,7049],{"class":73,"line":656},[71,7043,7044],{"class":507},"    app_name",[71,7046,511],{"class":99},[71,7048,514],{"class":439},[71,7050,517],{"class":445},[71,7052,7053],{"class":73,"line":690},[71,7054,559],{"class":445},[71,7056,7057],{"class":73,"line":719},[71,7058,464],{"emptyLinePlaceholder":463},[71,7060,7061,7063,7065,7067,7069],{"class":73,"line":724},[71,7062,469],{"class":445},[71,7064,176],{"class":472},[71,7066,475],{"class":445},[71,7068,449],{"class":439},[71,7070,491],{"class":445},[71,7072,7073,7075,7078,7080,7083,7085,7087,7089,7092,7094,7097,7099,7101,7103,7105,7107],{"class":73,"line":732},[71,7074,496],{"class":99},[71,7076,7077],{"class":439}," PageQuery",[71,7079,615],{"class":445},[71,7081,7082],{"class":507}," page",[71,7084,511],{"class":99},[71,7086,539],{"class":439},[71,7088,542],{"class":445},[71,7090,7091],{"class":439},"u32",[71,7093,548],{"class":445},[71,7095,7096],{"class":507}," size",[71,7098,511],{"class":99},[71,7100,539],{"class":439},[71,7102,542],{"class":445},[71,7104,7091],{"class":439},[71,7106,1730],{"class":445},[71,7108,1180],{"class":445},[71,7110,7111],{"class":73,"line":768},[71,7112,464],{"emptyLinePlaceholder":463},[71,7114,7115,7117,7119,7122,7124,7126,7128,7130,7132,7134,7137,7139,7141,7143,7145,7147,7149,7151,7153,7156,7158,7160,7162,7164,7166],{"class":73,"line":773},[71,7116,2790],{"class":99},[71,7118,2793],{"class":99},[71,7120,7121],{"class":88}," get_user",[71,7123,475],{"class":445},[71,7125,6871],{"class":88},[71,7127,475],{"class":445},[71,7129,2424],{"class":507},[71,7131,681],{"class":445},[71,7133,511],{"class":99},[71,7135,7136],{"class":439}," Path",[71,7138,542],{"class":445},[71,7140,2266],{"class":439},[71,7142,6563],{"class":445},[71,7144,577],{"class":99},[71,7146,514],{"class":439},[71,7148,615],{"class":445},[71,7150,1786],{"class":88},[71,7152,475],{"class":445},[71,7154,7155],{"class":92},"\"用户 ",[71,7157,446],{"class":445},[71,7159,2424],{"class":92},[71,7161,706],{"class":445},[71,7163,698],{"class":92},[71,7165,681],{"class":445},[71,7167,1180],{"class":445},[71,7169,7170],{"class":73,"line":781},[71,7171,464],{"emptyLinePlaceholder":463},[71,7173,7174,7176,7178,7181,7183,7186,7188,7191,7193,7195,7197,7199,7202,7204,7206,7208],{"class":73,"line":796},[71,7175,2790],{"class":99},[71,7177,2793],{"class":99},[71,7179,7180],{"class":88}," list_users",[71,7182,475],{"class":445},[71,7184,7185],{"class":88},"Query",[71,7187,475],{"class":445},[71,7189,7190],{"class":507},"q",[71,7192,681],{"class":445},[71,7194,511],{"class":99},[71,7196,6876],{"class":439},[71,7198,542],{"class":445},[71,7200,7201],{"class":439},"PageQuery",[71,7203,6563],{"class":445},[71,7205,577],{"class":99},[71,7207,514],{"class":439},[71,7209,502],{"class":445},[71,7211,7212,7215,7217,7220,7222,7225,7227,7229,7231,7234,7236,7239,7241,7244,7246,7248,7250,7252,7254,7257,7259,7261,7263,7266],{"class":73,"line":828},[71,7213,7214],{"class":88},"    format!",[71,7216,475],{"class":445},[71,7218,7219],{"class":92},"\"第 ",[71,7221,741],{"class":445},[71,7223,7224],{"class":92}," 页，每页 ",[71,7226,741],{"class":445},[71,7228,5654],{"class":92},[71,7230,452],{"class":445},[71,7232,7233],{"class":507}," q",[71,7235,626],{"class":99},[71,7237,7238],{"class":472},"page",[71,7240,626],{"class":99},[71,7242,7243],{"class":88},"unwrap_or",[71,7245,475],{"class":445},[71,7247,2061],{"class":88},[71,7249,2398],{"class":445},[71,7251,7233],{"class":507},[71,7253,626],{"class":99},[71,7255,7256],{"class":472},"size",[71,7258,626],{"class":99},[71,7260,7243],{"class":88},[71,7262,475],{"class":445},[71,7264,7265],{"class":88},"20",[71,7267,6693],{"class":445},[71,7269,7270],{"class":73,"line":848},[71,7271,559],{"class":445},[71,7273,7274],{"class":73,"line":857},[71,7275,464],{"emptyLinePlaceholder":463},[71,7277,7278,7280,7282,7285,7287,7290,7292,7295,7297,7299,7301,7303,7306,7308,7311,7313,7316,7318,7320,7322],{"class":73,"line":2022},[71,7279,2790],{"class":99},[71,7281,2793],{"class":99},[71,7283,7284],{"class":88}," visit",[71,7286,475],{"class":445},[71,7288,7289],{"class":88},"State",[71,7291,475],{"class":445},[71,7293,7294],{"class":507},"state",[71,7296,681],{"class":445},[71,7298,511],{"class":99},[71,7300,6881],{"class":439},[71,7302,542],{"class":445},[71,7304,7305],{"class":439},"AppState",[71,7307,548],{"class":445},[71,7309,7310],{"class":507}," headers",[71,7312,511],{"class":99},[71,7314,7315],{"class":439}," HeaderMap",[71,7317,681],{"class":445},[71,7319,577],{"class":99},[71,7321,514],{"class":439},[71,7323,502],{"class":445},[71,7325,7326,7328,7330,7332,7335,7337,7340,7342,7345,7347,7349,7351,7353,7355,7358,7360,7363,7365],{"class":73,"line":2027},[71,7327,604],{"class":99},[71,7329,4238],{"class":507},[71,7331,610],{"class":99},[71,7333,7334],{"class":507}," state",[71,7336,626],{"class":99},[71,7338,7339],{"class":472},"visit_count",[71,7341,626],{"class":99},[71,7343,7344],{"class":88},"fetch_add",[71,7346,475],{"class":445},[71,7348,2061],{"class":88},[71,7350,452],{"class":445},[71,7352,6988],{"class":439},[71,7354,442],{"class":99},[71,7356,7357],{"class":439},"Relaxed",[71,7359,681],{"class":445},[71,7361,7362],{"class":99}," +",[71,7364,4272],{"class":88},[71,7366,687],{"class":445},[71,7368,7369,7371,7374,7376,7378,7380,7382,7384,7387],{"class":73,"line":2035},[71,7370,604],{"class":99},[71,7372,7373],{"class":507}," ua",[71,7375,610],{"class":99},[71,7377,7310],{"class":507},[71,7379,626],{"class":99},[71,7381,5011],{"class":88},[71,7383,475],{"class":445},[71,7385,7386],{"class":92},"\"user-agent\"",[71,7388,1770],{"class":445},[71,7390,7391,7393,7396,7398,7401,7403,7405,7407,7409,7412,7414,7416,7419,7421,7423,7425,7427,7430],{"class":73,"line":2068},[71,7392,1775],{"class":99},[71,7394,7395],{"class":88},"and_then",[71,7397,475],{"class":445},[71,7399,7400],{"class":99},"|",[71,7402,3555],{"class":507},[71,7404,7400],{"class":99},[71,7406,801],{"class":507},[71,7408,626],{"class":99},[71,7410,7411],{"class":88},"to_str",[71,7413,574],{"class":445},[71,7415,626],{"class":99},[71,7417,7418],{"class":88},"ok",[71,7420,3044],{"class":445},[71,7422,626],{"class":99},[71,7424,7243],{"class":88},[71,7426,475],{"class":445},[71,7428,7429],{"class":92},"\"unknown\"",[71,7431,765],{"class":445},[71,7433,7434,7436,7438,7441,7443,7446,7448,7450,7452,7455,7457,7460,7462,7464,7466,7468,7470,7473],{"class":73,"line":2095},[71,7435,7214],{"class":88},[71,7437,475],{"class":445},[71,7439,7440],{"class":92},"\"[",[71,7442,741],{"class":445},[71,7444,7445],{"class":92},"] 第 ",[71,7447,446],{"class":445},[71,7449,4266],{"class":92},[71,7451,706],{"class":445},[71,7453,7454],{"class":92}," 次访问，UA: ",[71,7456,446],{"class":445},[71,7458,7459],{"class":92},"ua",[71,7461,706],{"class":445},[71,7463,698],{"class":92},[71,7465,452],{"class":445},[71,7467,7334],{"class":507},[71,7469,626],{"class":99},[71,7471,7472],{"class":472},"app_name",[71,7474,1770],{"class":445},[71,7476,7477],{"class":73,"line":2131},[71,7478,559],{"class":445},[71,7480,7481],{"class":73,"line":2136},[71,7482,464],{"emptyLinePlaceholder":463},[71,7484,7485,7487],{"class":73,"line":2143},[71,7486,998],{"class":77},[71,7488,7489],{"class":81}," 返回 (StatusCode, Json\u003CT>) 自定义状态码；impl IntoResponse 可以返回任意可转响应的类型\n",[71,7491,7492,7494,7496,7499,7501,7503,7505,7507,7509,7511,7513,7515,7517,7519,7521,7523,7525,7527,7529,7531,7533,7535,7537,7539,7541],{"class":73,"line":4626},[71,7493,2790],{"class":99},[71,7495,2793],{"class":99},[71,7497,7498],{"class":88}," maybe_found",[71,7500,475],{"class":445},[71,7502,7289],{"class":88},[71,7504,475],{"class":445},[71,7506,703],{"class":507},[71,7508,681],{"class":445},[71,7510,511],{"class":99},[71,7512,6881],{"class":439},[71,7514,542],{"class":445},[71,7516,7305],{"class":439},[71,7518,6563],{"class":445},[71,7520,577],{"class":99},[71,7522,1661],{"class":439},[71,7524,542],{"class":445},[71,7526,6544],{"class":439},[71,7528,542],{"class":445},[71,7530,5612],{"class":507},[71,7532,442],{"class":99},[71,7534,1143],{"class":439},[71,7536,548],{"class":445},[71,7538,6901],{"class":439},[71,7540,1730],{"class":445},[71,7542,502],{"class":445},[71,7544,7545,7547,7549,7551,7553,7555,7558,7560],{"class":73,"line":4649},[71,7546,1894],{"class":99},[71,7548,661],{"class":507},[71,7550,626],{"class":99},[71,7552,7472],{"class":472},[71,7554,626],{"class":99},[71,7556,7557],{"class":88},"is_empty",[71,7559,574],{"class":445},[71,7561,502],{"class":445},[71,7563,7564,7566,7568,7571,7574,7576,7578],{"class":73,"line":4654},[71,7565,2546],{"class":439},[71,7567,475],{"class":445},[71,7569,7570],{"class":439},"StatusCode",[71,7572,7573],{"class":99},"::NOT_FOUND",[71,7575,681],{"class":445},[71,7577,551],{"class":77},[71,7579,7580],{"class":81}," 返回 Err(StatusCode) 直接成为对应状态码的空响应\n",[71,7582,7583,7586,7589],{"class":73,"line":4662},[71,7584,7585],{"class":445},"    }",[71,7587,7588],{"class":99}," else",[71,7590,502],{"class":445},[71,7592,7593,7595,7597,7599,7601,7603,7605,7607,7610,7613,7615,7617],{"class":73,"line":4698},[71,7594,2634],{"class":439},[71,7596,475],{"class":445},[71,7598,6544],{"class":88},[71,7600,475],{"class":445},[71,7602,5612],{"class":439},[71,7604,442],{"class":99},[71,7606,1396],{"class":88},[71,7608,7609],{"class":445},"({",[71,7611,7612],{"class":92},"\"ok\"",[71,7614,511],{"class":99},[71,7616,1433],{"class":88},[71,7618,7619],{"class":445},"})))\n",[71,7621,7622],{"class":73,"line":4723},[71,7623,1935],{"class":445},[71,7625,7626],{"class":73,"line":4741},[71,7627,559],{"class":445},[71,7629,7630],{"class":73,"line":4763},[71,7631,464],{"emptyLinePlaceholder":463},[71,7633,7634,7636,7638,7640,7642],{"class":73,"line":4787},[71,7635,469],{"class":445},[71,7637,2777],{"class":472},[71,7639,442],{"class":99},[71,7641,2782],{"class":472},[71,7643,2785],{"class":445},[71,7645,7647,7649,7651,7653,7655],{"class":73,"line":7646},42,[71,7648,2790],{"class":99},[71,7650,2793],{"class":99},[71,7652,571],{"class":88},[71,7654,574],{"class":445},[71,7656,502],{"class":445},[71,7658,7660,7662,7664,7666,7668],{"class":73,"line":7659},43,[71,7661,604],{"class":99},[71,7663,7334],{"class":507},[71,7665,610],{"class":99},[71,7667,7022],{"class":439},[71,7669,502],{"class":445},[71,7671,7673,7676,7678,7680,7682,7684,7686,7688,7690,7692,7694,7696],{"class":73,"line":7672},44,[71,7674,7675],{"class":507},"        visit_count",[71,7677,511],{"class":99},[71,7679,4125],{"class":439},[71,7681,442],{"class":99},[71,7683,3876],{"class":88},[71,7685,475],{"class":445},[71,7687,6983],{"class":439},[71,7689,442],{"class":99},[71,7691,3876],{"class":88},[71,7693,475],{"class":445},[71,7695,1329],{"class":88},[71,7697,7698],{"class":445},")),\n",[71,7700,7702,7705,7707,7710,7712,7714],{"class":73,"line":7701},45,[71,7703,7704],{"class":507},"        app_name",[71,7706,511],{"class":99},[71,7708,7709],{"class":92}," \"demo\"",[71,7711,626],{"class":99},[71,7713,629],{"class":88},[71,7715,7716],{"class":445},"(),\n",[71,7718,7720],{"class":73,"line":7719},46,[71,7721,7722],{"class":445},"    };\n",[71,7724,7726],{"class":73,"line":7725},47,[71,7727,464],{"emptyLinePlaceholder":463},[71,7729,7731,7733,7735,7737,7739,7741,7743],{"class":73,"line":7730},48,[71,7732,604],{"class":99},[71,7734,6658],{"class":507},[71,7736,610],{"class":99},[71,7738,6340],{"class":439},[71,7740,442],{"class":99},[71,7742,3876],{"class":88},[71,7744,3983],{"class":445},[71,7746,7748,7750,7752,7754,7757,7759,7761,7763,7765,7767,7769,7771,7774,7776,7778],{"class":73,"line":7747},49,[71,7749,1775],{"class":99},[71,7751,6675],{"class":88},[71,7753,475],{"class":445},[71,7755,7756],{"class":92},"\"\u002Fusers\u002F",[71,7758,446],{"class":445},[71,7760,2424],{"class":92},[71,7762,706],{"class":445},[71,7764,698],{"class":92},[71,7766,452],{"class":445},[71,7768,6685],{"class":88},[71,7770,475],{"class":445},[71,7772,7773],{"class":507},"get_user",[71,7775,1803],{"class":445},[71,7777,1032],{"class":77},[71,7779,7780],{"class":81}," {id} 与 Path\u003Cu64> 对应，类型不符自动 400\n",[71,7782,7784,7786,7788,7790,7792,7794,7796,7798,7801],{"class":73,"line":7783},50,[71,7785,1775],{"class":99},[71,7787,6675],{"class":88},[71,7789,475],{"class":445},[71,7791,6726],{"class":92},[71,7793,452],{"class":445},[71,7795,6685],{"class":88},[71,7797,475],{"class":445},[71,7799,7800],{"class":507},"list_users",[71,7802,6693],{"class":445},[71,7804,7806,7808,7810,7812,7815,7817,7819,7821,7824],{"class":73,"line":7805},51,[71,7807,1775],{"class":99},[71,7809,6675],{"class":88},[71,7811,475],{"class":445},[71,7813,7814],{"class":92},"\"\u002Fvisit\"",[71,7816,452],{"class":445},[71,7818,6685],{"class":88},[71,7820,475],{"class":445},[71,7822,7823],{"class":507},"visit",[71,7825,6693],{"class":445},[71,7827,7829,7831,7833,7835,7838,7840,7842,7844,7847],{"class":73,"line":7828},52,[71,7830,1775],{"class":99},[71,7832,6675],{"class":88},[71,7834,475],{"class":445},[71,7836,7837],{"class":92},"\"\u002Ffound\"",[71,7839,452],{"class":445},[71,7841,6685],{"class":88},[71,7843,475],{"class":445},[71,7845,7846],{"class":507},"maybe_found",[71,7848,6693],{"class":445},[71,7850,7852,7854,7857,7859,7861,7863,7866],{"class":73,"line":7851},53,[71,7853,1775],{"class":99},[71,7855,7856],{"class":88},"with_state",[71,7858,475],{"class":445},[71,7860,7294],{"class":507},[71,7862,711],{"class":445},[71,7864,7865],{"class":77},"                            \u002F\u002F",[71,7867,7868],{"class":81}," 注入状态，handler 用 State 提取\n",[71,7870,7872],{"class":73,"line":7871},54,[71,7873,464],{"emptyLinePlaceholder":463},[71,7875,7877,7879,7881,7883,7885,7887,7889,7891,7893,7895,7897,7899,7901,7903,7905,7907],{"class":73,"line":7876},55,[71,7878,604],{"class":99},[71,7880,6755],{"class":507},[71,7882,610],{"class":99},[71,7884,113],{"class":439},[71,7886,442],{"class":99},[71,7888,6764],{"class":439},[71,7890,442],{"class":99},[71,7892,6769],{"class":439},[71,7894,442],{"class":99},[71,7896,6774],{"class":88},[71,7898,475],{"class":445},[71,7900,6779],{"class":92},[71,7902,681],{"class":445},[71,7904,2915],{"class":99},[71,7906,1496],{"class":88},[71,7908,1499],{"class":445},[71,7910,7912,7914,7916,7918,7920,7922,7924,7926,7928,7930,7932],{"class":73,"line":7911},56,[71,7913,6803],{"class":439},[71,7915,442],{"class":99},[71,7917,6808],{"class":88},[71,7919,475],{"class":445},[71,7921,6813],{"class":507},[71,7923,452],{"class":445},[71,7925,6658],{"class":507},[71,7927,681],{"class":445},[71,7929,2915],{"class":99},[71,7931,1496],{"class":88},[71,7933,1499],{"class":445},[71,7935,7937],{"class":73,"line":7936},57,[71,7938,559],{"class":445},[57,7940,7942],{"id":7941},"_54-统一错误响应与中间件","5.4 统一错误响应与中间件",[62,7944,7946],{"className":427,"code":7945,"language":429,"meta":67,"style":67},"use axum::{\n    http::StatusCode,\n    response::{IntoResponse, Response},\n    Json, Router, routing::get, middleware,\n};\nuse serde_json::json;\n\n\u002F\u002F 应用错误类型：实现 IntoResponse 后，handler 直接返回 Result\u003CT, AppError>\n\u002F\u002F 为什么？否则每个 handler 都要手写 try\u002Fmatch 转 HTTP 响应，业务代码被噪音淹没\nenum AppError { NotFound(String), Internal(anyhow::Error) }\n\nimpl IntoResponse for AppError {\n    fn into_response(self) -> Response {\n        let (status, msg) = match self {\n            AppError::NotFound(m) => (StatusCode::NOT_FOUND, m),\n            AppError::Internal(e) => {\n                eprintln!(\"内部错误: {e:#}\"); \u002F\u002F 详细错误只进服务端日志\n                (StatusCode::INTERNAL_SERVER_ERROR, \"服务器内部错误\".into())\n                \u002F\u002F 为什么不把内部错误详情返回给客户端？——防止泄露路径、SQL 等敏感信息\n            }\n        };\n        (status, Json(json!({ \"error\": msg }))).into_response()\n    }\n}\n\n\u002F\u002F From 转换让 ? 直接可用\nimpl From\u003Canyhow::Error> for AppError {\n    fn from(e: anyhow::Error) -> Self { AppError::Internal(e) }\n}\n\nasync fn handler() -> Result\u003CString, AppError> {\n    Err(AppError::NotFound(\"东西不存在\".into()))\n}\n\n\u002F\u002F 简单中间件：函数式 middleware\nasync fn log_requests(req: axum::extract::Request, next: middleware::Next) -> Response {\n    let start = std::time::Instant::now();\n    let method = req.method().clone();\n    let path = req.uri().path().to_string();\n    let resp = next.run(req).await;          \u002F\u002F 调用链路的下一层\n    println!(\"{method} {path} -> {} ({:?})\", resp.status(), start.elapsed());\n    resp\n}\n\n#[tokio::main]\nasync fn main() {\n    let app = Router::new()\n        .route(\"\u002F\", get(handler))\n        .layer(middleware::from_fn(log_requests)) \u002F\u002F 挂在整个 Router 上\n        .fallback(|| async { (StatusCode::NOT_FOUND, \"路由不存在\") }); \u002F\u002F 自定义 404\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    axum::serve(listener, app).await.unwrap();\n}\n",[52,7947,7948,7958,7968,7987,8010,8014,8026,8030,8037,8044,8078,8082,8097,8118,8143,8174,8193,8219,8240,8248,8253,8258,8292,8296,8300,8304,8311,8334,8376,8380,8384,8411,8437,8441,8445,8452,8502,8529,8554,8583,8613,8668,8673,8677,8681,8693,8705,8721,8742,8771,8808,8812,8846,8870],{"__ignoreMap":67},[71,7949,7950,7952,7954,7956],{"class":73,"line":74},[71,7951,436],{"class":99},[71,7953,6317],{"class":439},[71,7955,442],{"class":99},[71,7957,6859],{"class":445},[71,7959,7960,7962,7964,7966],{"class":73,"line":85},[71,7961,6889],{"class":439},[71,7963,442],{"class":99},[71,7965,7570],{"class":439},[71,7967,517],{"class":445},[71,7969,7970,7973,7975,7977,7980,7982,7985],{"class":73,"line":106},[71,7971,7972],{"class":439},"    response",[71,7974,442],{"class":99},[71,7976,446],{"class":445},[71,7978,7979],{"class":439},"IntoResponse",[71,7981,452],{"class":445},[71,7983,7984],{"class":439}," Response",[71,7986,6884],{"class":445},[71,7988,7989,7991,7993,7995,7997,7999,8001,8003,8005,8008],{"class":73,"line":121},[71,7990,6505],{"class":439},[71,7992,452],{"class":445},[71,7994,6340],{"class":439},[71,7996,452],{"class":445},[71,7998,6916],{"class":439},[71,8000,442],{"class":99},[71,8002,5011],{"class":472},[71,8004,452],{"class":445},[71,8006,8007],{"class":472}," middleware",[71,8009,517],{"class":445},[71,8011,8012],{"class":73,"line":259},[71,8013,458],{"class":445},[71,8015,8016,8018,8020,8022,8024],{"class":73,"line":273},[71,8017,436],{"class":99},[71,8019,580],{"class":439},[71,8021,442],{"class":99},[71,8023,5136],{"class":472},[71,8025,687],{"class":445},[71,8027,8028],{"class":73,"line":286},[71,8029,464],{"emptyLinePlaceholder":463},[71,8031,8032,8034],{"class":73,"line":300},[71,8033,998],{"class":77},[71,8035,8036],{"class":81}," 应用错误类型：实现 IntoResponse 后，handler 直接返回 Result\u003CT, AppError>\n",[71,8038,8039,8041],{"class":73,"line":316},[71,8040,998],{"class":77},[71,8042,8043],{"class":81}," 为什么？否则每个 handler 都要手写 try\u002Fmatch 转 HTTP 响应，业务代码被噪音淹没\n",[71,8045,8046,8049,8052,8054,8057,8059,8061,8063,8066,8068,8070,8072,8074,8076],{"class":73,"line":330},[71,8047,8048],{"class":99},"enum",[71,8050,8051],{"class":439}," AppError",[71,8053,615],{"class":445},[71,8055,8056],{"class":88}," NotFound",[71,8058,475],{"class":445},[71,8060,545],{"class":439},[71,8062,2398],{"class":445},[71,8064,8065],{"class":88}," Internal",[71,8067,475],{"class":445},[71,8069,1600],{"class":439},[71,8071,442],{"class":99},[71,8073,2192],{"class":439},[71,8075,681],{"class":445},[71,8077,1180],{"class":445},[71,8079,8080],{"class":73,"line":352},[71,8081,464],{"emptyLinePlaceholder":463},[71,8083,8084,8087,8090,8093,8095],{"class":73,"line":601},[71,8085,8086],{"class":99},"impl",[71,8088,8089],{"class":439}," IntoResponse",[71,8091,8092],{"class":99}," for",[71,8094,8051],{"class":439},[71,8096,502],{"class":445},[71,8098,8099,8102,8105,8107,8110,8112,8114,8116],{"class":73,"line":656},[71,8100,8101],{"class":99},"    fn",[71,8103,8104],{"class":88}," into_response",[71,8106,475],{"class":445},[71,8108,8109],{"class":507},"self",[71,8111,681],{"class":445},[71,8113,577],{"class":99},[71,8115,7984],{"class":439},[71,8117,502],{"class":445},[71,8119,8120,8122,8124,8126,8128,8131,8133,8135,8138,8141],{"class":73,"line":690},[71,8121,4187],{"class":99},[71,8123,3162],{"class":445},[71,8125,5487],{"class":507},[71,8127,452],{"class":445},[71,8129,8130],{"class":507}," msg",[71,8132,681],{"class":445},[71,8134,610],{"class":99},[71,8136,8137],{"class":99}," match",[71,8139,8140],{"class":507}," self",[71,8142,502],{"class":445},[71,8144,8145,8148,8150,8152,8154,8157,8159,8161,8163,8165,8167,8169,8172],{"class":73,"line":719},[71,8146,8147],{"class":439},"            AppError",[71,8149,442],{"class":99},[71,8151,2475],{"class":88},[71,8153,475],{"class":445},[71,8155,8156],{"class":507},"m",[71,8158,681],{"class":445},[71,8160,2563],{"class":99},[71,8162,3162],{"class":445},[71,8164,7570],{"class":439},[71,8166,7573],{"class":99},[71,8168,452],{"class":445},[71,8170,8171],{"class":507}," m",[71,8173,2269],{"class":445},[71,8175,8176,8178,8180,8183,8185,8187,8189,8191],{"class":73,"line":724},[71,8177,8147],{"class":439},[71,8179,442],{"class":99},[71,8181,8182],{"class":88},"Internal",[71,8184,475],{"class":445},[71,8186,2047],{"class":507},[71,8188,681],{"class":445},[71,8190,2563],{"class":99},[71,8192,502],{"class":445},[71,8194,8195,8198,8200,8203,8205,8208,8210,8212,8214,8216],{"class":73,"line":732},[71,8196,8197],{"class":88},"                eprintln!",[71,8199,475],{"class":445},[71,8201,8202],{"class":92},"\"内部错误: ",[71,8204,446],{"class":445},[71,8206,8207],{"class":92},"e:#",[71,8209,706],{"class":445},[71,8211,698],{"class":92},[71,8213,711],{"class":445},[71,8215,551],{"class":77},[71,8217,8218],{"class":81}," 详细错误只进服务端日志\n",[71,8220,8221,8224,8226,8229,8231,8234,8236,8238],{"class":73,"line":768},[71,8222,8223],{"class":445},"                (",[71,8225,7570],{"class":439},[71,8227,8228],{"class":99},"::INTERNAL_SERVER_ERROR",[71,8230,452],{"class":445},[71,8232,8233],{"class":92}," \"服务器内部错误\"",[71,8235,626],{"class":99},[71,8237,629],{"class":88},[71,8239,2500],{"class":445},[71,8241,8242,8245],{"class":73,"line":773},[71,8243,8244],{"class":77},"                \u002F\u002F",[71,8246,8247],{"class":81}," 为什么不把内部错误详情返回给客户端？——防止泄露路径、SQL 等敏感信息\n",[71,8249,8250],{"class":73,"line":781},[71,8251,8252],{"class":445},"            }\n",[71,8254,8255],{"class":73,"line":796},[71,8256,8257],{"class":445},"        };\n",[71,8259,8260,8263,8265,8267,8269,8271,8273,8275,8278,8280,8282,8285,8287,8290],{"class":73,"line":828},[71,8261,8262],{"class":445},"        (",[71,8264,5487],{"class":507},[71,8266,452],{"class":445},[71,8268,6345],{"class":88},[71,8270,475],{"class":445},[71,8272,1396],{"class":88},[71,8274,7609],{"class":445},[71,8276,8277],{"class":92}," \"error\"",[71,8279,511],{"class":99},[71,8281,8130],{"class":507},[71,8283,8284],{"class":445}," })))",[71,8286,626],{"class":99},[71,8288,8289],{"class":88},"into_response",[71,8291,3983],{"class":445},[71,8293,8294],{"class":73,"line":848},[71,8295,1935],{"class":445},[71,8297,8298],{"class":73,"line":857},[71,8299,559],{"class":445},[71,8301,8302],{"class":73,"line":2022},[71,8303,464],{"emptyLinePlaceholder":463},[71,8305,8306,8308],{"class":73,"line":2027},[71,8307,998],{"class":77},[71,8309,8310],{"class":81}," From 转换让 ? 直接可用\n",[71,8312,8313,8315,8318,8320,8322,8324,8326,8328,8330,8332],{"class":73,"line":2035},[71,8314,8086],{"class":99},[71,8316,8317],{"class":439}," From",[71,8319,542],{"class":445},[71,8321,1600],{"class":507},[71,8323,442],{"class":99},[71,8325,2192],{"class":439},[71,8327,1730],{"class":445},[71,8329,8092],{"class":99},[71,8331,8051],{"class":439},[71,8333,502],{"class":445},[71,8335,8336,8338,8341,8343,8345,8347,8349,8351,8353,8355,8357,8360,8362,8364,8366,8368,8370,8372,8374],{"class":73,"line":2068},[71,8337,8101],{"class":99},[71,8339,8340],{"class":88}," from",[71,8342,475],{"class":445},[71,8344,2047],{"class":507},[71,8346,511],{"class":99},[71,8348,1649],{"class":439},[71,8350,442],{"class":99},[71,8352,2192],{"class":439},[71,8354,681],{"class":445},[71,8356,577],{"class":99},[71,8358,8359],{"class":507}," Self",[71,8361,615],{"class":445},[71,8363,8051],{"class":439},[71,8365,442],{"class":99},[71,8367,8182],{"class":88},[71,8369,475],{"class":445},[71,8371,2047],{"class":507},[71,8373,681],{"class":445},[71,8375,1180],{"class":445},[71,8377,8378],{"class":73,"line":2095},[71,8379,559],{"class":445},[71,8381,8382],{"class":73,"line":2131},[71,8383,464],{"emptyLinePlaceholder":463},[71,8385,8386,8388,8390,8393,8395,8397,8399,8401,8403,8405,8407,8409],{"class":73,"line":2136},[71,8387,2790],{"class":99},[71,8389,2793],{"class":99},[71,8391,8392],{"class":88}," handler",[71,8394,574],{"class":445},[71,8396,577],{"class":99},[71,8398,1661],{"class":439},[71,8400,542],{"class":445},[71,8402,545],{"class":439},[71,8404,452],{"class":445},[71,8406,8051],{"class":439},[71,8408,1730],{"class":445},[71,8410,502],{"class":445},[71,8412,8413,8416,8418,8421,8423,8425,8427,8430,8432,8434],{"class":73,"line":2143},[71,8414,8415],{"class":439},"    Err",[71,8417,475],{"class":445},[71,8419,8420],{"class":439},"AppError",[71,8422,442],{"class":99},[71,8424,2475],{"class":88},[71,8426,475],{"class":445},[71,8428,8429],{"class":92},"\"东西不存在\"",[71,8431,626],{"class":99},[71,8433,629],{"class":88},[71,8435,8436],{"class":445},"()))\n",[71,8438,8439],{"class":73,"line":4626},[71,8440,559],{"class":445},[71,8442,8443],{"class":73,"line":4649},[71,8444,464],{"emptyLinePlaceholder":463},[71,8446,8447,8449],{"class":73,"line":4654},[71,8448,998],{"class":77},[71,8450,8451],{"class":81}," 简单中间件：函数式 middleware\n",[71,8453,8454,8456,8458,8461,8463,8466,8468,8470,8472,8475,8477,8480,8482,8485,8487,8489,8491,8494,8496,8498,8500],{"class":73,"line":4662},[71,8455,2790],{"class":99},[71,8457,2793],{"class":99},[71,8459,8460],{"class":88}," log_requests",[71,8462,475],{"class":445},[71,8464,8465],{"class":507},"req",[71,8467,511],{"class":99},[71,8469,6317],{"class":439},[71,8471,442],{"class":99},[71,8473,8474],{"class":439},"extract",[71,8476,442],{"class":99},[71,8478,8479],{"class":439},"Request",[71,8481,452],{"class":445},[71,8483,8484],{"class":507}," next",[71,8486,511],{"class":99},[71,8488,8007],{"class":439},[71,8490,442],{"class":99},[71,8492,8493],{"class":439},"Next",[71,8495,681],{"class":445},[71,8497,577],{"class":99},[71,8499,7984],{"class":439},[71,8501,502],{"class":445},[71,8503,8504,8506,8509,8511,8513,8515,8517,8519,8522,8524,8527],{"class":73,"line":4698},[71,8505,604],{"class":99},[71,8507,8508],{"class":507}," start",[71,8510,610],{"class":99},[71,8512,1120],{"class":439},[71,8514,442],{"class":99},[71,8516,2845],{"class":439},[71,8518,442],{"class":99},[71,8520,8521],{"class":439},"Instant",[71,8523,442],{"class":99},[71,8525,8526],{"class":88},"now",[71,8528,1499],{"class":445},[71,8530,8531,8533,8536,8538,8541,8543,8546,8548,8550,8552],{"class":73,"line":4723},[71,8532,604],{"class":99},[71,8534,8535],{"class":507}," method",[71,8537,610],{"class":99},[71,8539,8540],{"class":507}," req",[71,8542,626],{"class":99},[71,8544,8545],{"class":88},"method",[71,8547,574],{"class":445},[71,8549,626],{"class":99},[71,8551,4199],{"class":88},[71,8553,1499],{"class":445},[71,8555,8556,8558,8560,8562,8564,8566,8569,8571,8573,8575,8577,8579,8581],{"class":73,"line":4741},[71,8557,604],{"class":99},[71,8559,5764],{"class":507},[71,8561,610],{"class":99},[71,8563,8540],{"class":507},[71,8565,626],{"class":99},[71,8567,8568],{"class":88},"uri",[71,8570,574],{"class":445},[71,8572,626],{"class":99},[71,8574,1709],{"class":88},[71,8576,574],{"class":445},[71,8578,626],{"class":99},[71,8580,670],{"class":88},[71,8582,1499],{"class":445},[71,8584,8585,8587,8589,8591,8593,8595,8598,8600,8602,8604,8606,8608,8610],{"class":73,"line":4763},[71,8586,604],{"class":99},[71,8588,5383],{"class":507},[71,8590,610],{"class":99},[71,8592,8484],{"class":507},[71,8594,626],{"class":99},[71,8596,8597],{"class":88},"run",[71,8599,475],{"class":445},[71,8601,8465],{"class":507},[71,8603,681],{"class":445},[71,8605,2878],{"class":99},[71,8607,3029],{"class":445},[71,8609,1032],{"class":77},[71,8611,8612],{"class":81}," 调用链路的下一层\n",[71,8614,8615,8617,8619,8621,8623,8625,8627,8629,8631,8633,8636,8638,8640,8642,8644,8646,8649,8651,8653,8655,8657,8659,8661,8663,8666],{"class":73,"line":4787},[71,8616,693],{"class":88},[71,8618,475],{"class":445},[71,8620,698],{"class":92},[71,8622,446],{"class":445},[71,8624,8545],{"class":92},[71,8626,706],{"class":445},[71,8628,615],{"class":445},[71,8630,1709],{"class":92},[71,8632,706],{"class":445},[71,8634,8635],{"class":92}," -> ",[71,8637,741],{"class":445},[71,8639,3162],{"class":92},[71,8641,446],{"class":445},[71,8643,2107],{"class":92},[71,8645,706],{"class":445},[71,8647,8648],{"class":92},")\"",[71,8650,452],{"class":445},[71,8652,5383],{"class":507},[71,8654,626],{"class":99},[71,8656,5487],{"class":88},[71,8658,632],{"class":445},[71,8660,8508],{"class":507},[71,8662,626],{"class":99},[71,8664,8665],{"class":88},"elapsed",[71,8667,2019],{"class":445},[71,8669,8670],{"class":73,"line":7646},[71,8671,8672],{"class":507},"    resp\n",[71,8674,8675],{"class":73,"line":7659},[71,8676,559],{"class":445},[71,8678,8679],{"class":73,"line":7672},[71,8680,464],{"emptyLinePlaceholder":463},[71,8682,8683,8685,8687,8689,8691],{"class":73,"line":7701},[71,8684,469],{"class":445},[71,8686,2777],{"class":472},[71,8688,442],{"class":99},[71,8690,2782],{"class":472},[71,8692,2785],{"class":445},[71,8694,8695,8697,8699,8701,8703],{"class":73,"line":7719},[71,8696,2790],{"class":99},[71,8698,2793],{"class":99},[71,8700,571],{"class":88},[71,8702,574],{"class":445},[71,8704,502],{"class":445},[71,8706,8707,8709,8711,8713,8715,8717,8719],{"class":73,"line":7725},[71,8708,604],{"class":99},[71,8710,6658],{"class":507},[71,8712,610],{"class":99},[71,8714,6340],{"class":439},[71,8716,442],{"class":99},[71,8718,3876],{"class":88},[71,8720,3983],{"class":445},[71,8722,8723,8725,8727,8729,8731,8733,8735,8737,8740],{"class":73,"line":7730},[71,8724,1775],{"class":99},[71,8726,6675],{"class":88},[71,8728,475],{"class":445},[71,8730,6680],{"class":92},[71,8732,452],{"class":445},[71,8734,6685],{"class":88},[71,8736,475],{"class":445},[71,8738,8739],{"class":507},"handler",[71,8741,6693],{"class":445},[71,8743,8744,8746,8749,8751,8754,8756,8759,8761,8764,8766,8768],{"class":73,"line":7747},[71,8745,1775],{"class":99},[71,8747,8748],{"class":88},"layer",[71,8750,475],{"class":445},[71,8752,8753],{"class":439},"middleware",[71,8755,442],{"class":99},[71,8757,8758],{"class":88},"from_fn",[71,8760,475],{"class":445},[71,8762,8763],{"class":507},"log_requests",[71,8765,1803],{"class":445},[71,8767,551],{"class":77},[71,8769,8770],{"class":81}," 挂在整个 Router 上\n",[71,8772,8773,8775,8778,8780,8782,8785,8787,8789,8791,8793,8795,8798,8800,8803,8805],{"class":73,"line":7783},[71,8774,1775],{"class":99},[71,8776,8777],{"class":88},"fallback",[71,8779,475],{"class":445},[71,8781,1783],{"class":99},[71,8783,8784],{"class":99}," async",[71,8786,615],{"class":445},[71,8788,3162],{"class":445},[71,8790,7570],{"class":439},[71,8792,7573],{"class":99},[71,8794,452],{"class":445},[71,8796,8797],{"class":92}," \"路由不存在\"",[71,8799,681],{"class":445},[71,8801,8802],{"class":445}," });",[71,8804,551],{"class":77},[71,8806,8807],{"class":81}," 自定义 404\n",[71,8809,8810],{"class":73,"line":7805},[71,8811,464],{"emptyLinePlaceholder":463},[71,8813,8814,8816,8818,8820,8822,8824,8826,8828,8830,8832,8834,8836,8838,8840,8842,8844],{"class":73,"line":7828},[71,8815,604],{"class":99},[71,8817,6755],{"class":507},[71,8819,610],{"class":99},[71,8821,113],{"class":439},[71,8823,442],{"class":99},[71,8825,6764],{"class":439},[71,8827,442],{"class":99},[71,8829,6769],{"class":439},[71,8831,442],{"class":99},[71,8833,6774],{"class":88},[71,8835,475],{"class":445},[71,8837,6779],{"class":92},[71,8839,681],{"class":445},[71,8841,2915],{"class":99},[71,8843,1496],{"class":88},[71,8845,1499],{"class":445},[71,8847,8848,8850,8852,8854,8856,8858,8860,8862,8864,8866,8868],{"class":73,"line":7851},[71,8849,6803],{"class":439},[71,8851,442],{"class":99},[71,8853,6808],{"class":88},[71,8855,475],{"class":445},[71,8857,6813],{"class":507},[71,8859,452],{"class":445},[71,8861,6658],{"class":507},[71,8863,681],{"class":445},[71,8865,2915],{"class":99},[71,8867,1496],{"class":88},[71,8869,1499],{"class":445},[71,8871,8872],{"class":73,"line":7871},[71,8873,559],{"class":445},[57,8875,8877],{"id":8876},"_55-路由嵌套与静态文件","5.5 路由嵌套与静态文件",[62,8879,8881],{"className":427,"code":8880,"language":429,"meta":67,"style":67},"use axum::{Router, routing::get};\n\nfn api_routes() -> Router {\n    Router::new().route(\"\u002Fhealth\", get(|| async { \"ok\" }))\n}\n\nfn app() -> Router {\n    Router::new()\n        .nest(\"\u002Fapi\u002Fv1\", api_routes())      \u002F\u002F 嵌套：\u002Fapi\u002Fv1\u002Fhealth\n        \u002F\u002F .nest_service(\"\u002Fstatic\", tower_http::services::ServeDir::new(\"static\")) \u002F\u002F 静态文件需 tower-http\n}\n",[52,8882,8883,8906,8910,8925,8963,8967,8971,8985,8995,9019,9026],{"__ignoreMap":67},[71,8884,8885,8887,8889,8891,8893,8896,8898,8900,8902,8904],{"class":73,"line":74},[71,8886,436],{"class":99},[71,8888,6317],{"class":439},[71,8890,442],{"class":99},[71,8892,446],{"class":445},[71,8894,8895],{"class":439},"Router",[71,8897,452],{"class":445},[71,8899,6916],{"class":439},[71,8901,442],{"class":99},[71,8903,5011],{"class":472},[71,8905,458],{"class":445},[71,8907,8908],{"class":73,"line":85},[71,8909,464],{"emptyLinePlaceholder":463},[71,8911,8912,8914,8917,8919,8921,8923],{"class":73,"line":106},[71,8913,568],{"class":99},[71,8915,8916],{"class":88}," api_routes",[71,8918,574],{"class":445},[71,8920,577],{"class":99},[71,8922,6340],{"class":439},[71,8924,502],{"class":445},[71,8926,8927,8930,8932,8934,8936,8938,8940,8942,8945,8947,8949,8951,8953,8955,8957,8960],{"class":73,"line":121},[71,8928,8929],{"class":439},"    Router",[71,8931,442],{"class":99},[71,8933,3876],{"class":88},[71,8935,574],{"class":445},[71,8937,626],{"class":99},[71,8939,6675],{"class":88},[71,8941,475],{"class":445},[71,8943,8944],{"class":92},"\"\u002Fhealth\"",[71,8946,452],{"class":445},[71,8948,6685],{"class":88},[71,8950,475],{"class":445},[71,8952,1783],{"class":99},[71,8954,8784],{"class":99},[71,8956,615],{"class":445},[71,8958,8959],{"class":92}," \"ok\"",[71,8961,8962],{"class":445}," }))\n",[71,8964,8965],{"class":73,"line":259},[71,8966,559],{"class":445},[71,8968,8969],{"class":73,"line":273},[71,8970,464],{"emptyLinePlaceholder":463},[71,8972,8973,8975,8977,8979,8981,8983],{"class":73,"line":286},[71,8974,568],{"class":99},[71,8976,6658],{"class":88},[71,8978,574],{"class":445},[71,8980,577],{"class":99},[71,8982,6340],{"class":439},[71,8984,502],{"class":445},[71,8986,8987,8989,8991,8993],{"class":73,"line":300},[71,8988,8929],{"class":439},[71,8990,442],{"class":99},[71,8992,3876],{"class":88},[71,8994,3983],{"class":445},[71,8996,8997,8999,9002,9004,9007,9009,9011,9013,9016],{"class":73,"line":316},[71,8998,1775],{"class":99},[71,9000,9001],{"class":88},"nest",[71,9003,475],{"class":445},[71,9005,9006],{"class":92},"\"\u002Fapi\u002Fv1\"",[71,9008,452],{"class":445},[71,9010,8916],{"class":88},[71,9012,3044],{"class":445},[71,9014,9015],{"class":77},"      \u002F\u002F",[71,9017,9018],{"class":81}," 嵌套：\u002Fapi\u002Fv1\u002Fhealth\n",[71,9020,9021,9023],{"class":73,"line":330},[71,9022,2825],{"class":77},[71,9024,9025],{"class":81}," .nest_service(\"\u002Fstatic\", tower_http::services::ServeDir::new(\"static\")) \u002F\u002F 静态文件需 tower-http\n",[71,9027,9028],{"class":73,"line":352},[71,9029,559],{"class":445},[23,9031],{},[26,9033,9035],{"id":9034},"第-6-章-clap命令行参数解析","第 6 章 clap：命令行参数解析",[57,9037,9039],{"id":9038},"_61-它解决什么问题","6.1 它解决什么问题",[14,9041,9042,9043,9046,9047,9050,9051,9054],{},"解析 ",[52,9044,9045],{},"mytool --input a.txt -v"," 这类参数。clap 的 ",[18,9048,9049],{},"derive 模式","让你用结构体定义 CLI，自动生成 ",[52,9052,9053],{},"--help","、自动校验、自动类型转换。",[62,9056,9058],{"className":143,"code":9057,"language":145,"meta":67,"style":67},"[dependencies]\nclap = { version = \"4\", features = [\"derive\"] }\n",[52,9059,9060,9064],{"__ignoreMap":67},[71,9061,9062],{"class":73,"line":74},[71,9063,152],{},[71,9065,9066],{"class":73,"line":85},[71,9067,9068],{},"clap = { version = \"4\", features = [\"derive\"] }\n",[14,9070,9071,9074],{},[18,9072,9073],{},"为什么用 derive 而不是 builder？"," 结构体即文档：参数长什么样、什么类型、什么默认值，一眼看清，且解析结果就是强类型结构体，不存在「字符串忘了 parse」的 bug。",[57,9076,9078],{"id":9077},"_62-完整案例","6.2 完整案例",[62,9080,9082],{"className":427,"code":9081,"language":429,"meta":67,"style":67},"use clap::{Parser, Subcommand, ValueEnum};\n\n#[derive(Parser)]\n#[command(name = \"mytool\", version, about = \"一个示例工具\", long_about = None)]\nstruct Cli {\n    \u002F\u002F\u002F 输入文件（doc comment 自动成为帮助文本！）\n    #[arg(short, long)]\n    input: String,\n\n    \u002F\u002F\u002F 输出目录\n    #[arg(short, long, default_value = \".\u002Fout\")]   \u002F\u002F 不传时的默认值\n    output: String,\n\n    \u002F\u002F\u002F 详细日志，-v \u002F -vv \u002F -vvv 可叠加\n    #[arg(short, long, action = clap::ArgAction::Count)]\n    verbose: u8,\n\n    \u002F\u002F\u002F 并发数，1-32\n    #[arg(short, long, default_value_t = 4, value_parser = clap::value_parser!(u8).range(1..=32))]\n    jobs: u8,\n\n    \u002F\u002F\u002F 输出格式\n    #[arg(long, value_enum, default_value_t = Format::Json)]\n    format: Format,\n\n    #[command(subcommand)]\n    command: Commands,\n}\n\n#[derive(Clone, ValueEnum)]\nenum Format { Json, Text, Csv }\n\n#[derive(Subcommand)]\nenum Commands {\n    \u002F\u002F\u002F 处理文件\n    Process {\n        \u002F\u002F\u002F 是否强制覆盖\n        #[arg(short, long)]\n        force: bool,\n    },\n    \u002F\u002F\u002F 清理缓存（位置参数：直接写在命令后面，不需要 --）\n    Clean {\n        target: String,\n    },\n}\n\nfn main() {\n    let cli = Cli::parse(); \u002F\u002F 解析失败（缺参数\u002F类型错）自动打印错误和用法，退出码 2\n\n    match cli.command {\n        Commands::Process { force } => {\n            println!(\"处理 {} -> {} (force={force}, jobs={}, format={:?})\",\n                     cli.input, cli.output, cli.jobs, cli.format);\n        }\n        Commands::Clean { target } => println!(\"清理 {target}\"),\n    }\n}\n",[52,9083,9084,9110,9114,9126,9169,9178,9186,9205,9216,9220,9227,9258,9269,9273,9280,9315,9326,9330,9337,9402,9413,9417,9424,9455,9466,9470,9483,9495,9499,9503,9519,9541,9545,9558,9566,9573,9580,9588,9605,9617,9622,9629,9636,9647,9651,9655,9659,9669,9692,9696,9709,9730,9774,9813,9817,9853,9857],{"__ignoreMap":67},[71,9085,9086,9088,9091,9093,9095,9098,9100,9103,9105,9108],{"class":73,"line":74},[71,9087,436],{"class":99},[71,9089,9090],{"class":439}," clap",[71,9092,442],{"class":99},[71,9094,446],{"class":445},[71,9096,9097],{"class":439},"Parser",[71,9099,452],{"class":445},[71,9101,9102],{"class":439}," Subcommand",[71,9104,452],{"class":445},[71,9106,9107],{"class":439}," ValueEnum",[71,9109,458],{"class":445},[71,9111,9112],{"class":73,"line":85},[71,9113,464],{"emptyLinePlaceholder":463},[71,9115,9116,9118,9120,9122,9124],{"class":73,"line":106},[71,9117,469],{"class":445},[71,9119,176],{"class":472},[71,9121,475],{"class":445},[71,9123,9097],{"class":439},[71,9125,491],{"class":445},[71,9127,9128,9130,9133,9135,9138,9140,9143,9145,9148,9150,9153,9155,9158,9160,9163,9165,9167],{"class":73,"line":121},[71,9129,469],{"class":445},[71,9131,9132],{"class":472},"command",[71,9134,475],{"class":445},[71,9136,9137],{"class":472},"name ",[71,9139,909],{"class":99},[71,9141,9142],{"class":92}," \"mytool\"",[71,9144,452],{"class":445},[71,9146,9147],{"class":472}," version",[71,9149,452],{"class":445},[71,9151,9152],{"class":472}," about ",[71,9154,909],{"class":99},[71,9156,9157],{"class":92}," \"一个示例工具\"",[71,9159,452],{"class":445},[71,9161,9162],{"class":472}," long_about ",[71,9164,909],{"class":99},[71,9166,650],{"class":439},[71,9168,491],{"class":445},[71,9170,9171,9173,9176],{"class":73,"line":259},[71,9172,496],{"class":99},[71,9174,9175],{"class":439}," Cli",[71,9177,502],{"class":445},[71,9179,9180,9183],{"class":73,"line":273},[71,9181,9182],{"class":77},"    \u002F\u002F\u002F",[71,9184,9185],{"class":81}," 输入文件（doc comment 自动成为帮助文本！）\n",[71,9187,9188,9190,9193,9195,9198,9200,9203],{"class":73,"line":286},[71,9189,898],{"class":445},[71,9191,9192],{"class":472},"arg",[71,9194,475],{"class":445},[71,9196,9197],{"class":472},"short",[71,9199,452],{"class":445},[71,9201,9202],{"class":472}," long",[71,9204,491],{"class":445},[71,9206,9207,9210,9212,9214],{"class":73,"line":300},[71,9208,9209],{"class":507},"    input",[71,9211,511],{"class":99},[71,9213,514],{"class":439},[71,9215,517],{"class":445},[71,9217,9218],{"class":73,"line":316},[71,9219,464],{"emptyLinePlaceholder":463},[71,9221,9222,9224],{"class":73,"line":330},[71,9223,9182],{"class":77},[71,9225,9226],{"class":81}," 输出目录\n",[71,9228,9229,9231,9233,9235,9237,9239,9241,9243,9246,9248,9251,9253,9255],{"class":73,"line":352},[71,9230,898],{"class":445},[71,9232,9192],{"class":472},[71,9234,475],{"class":445},[71,9236,9197],{"class":472},[71,9238,452],{"class":445},[71,9240,9202],{"class":472},[71,9242,452],{"class":445},[71,9244,9245],{"class":472}," default_value ",[71,9247,909],{"class":99},[71,9249,9250],{"class":92}," \".\u002Fout\"",[71,9252,915],{"class":445},[71,9254,1886],{"class":77},[71,9256,9257],{"class":81}," 不传时的默认值\n",[71,9259,9260,9263,9265,9267],{"class":73,"line":601},[71,9261,9262],{"class":507},"    output",[71,9264,511],{"class":99},[71,9266,514],{"class":439},[71,9268,517],{"class":445},[71,9270,9271],{"class":73,"line":656},[71,9272,464],{"emptyLinePlaceholder":463},[71,9274,9275,9277],{"class":73,"line":690},[71,9276,9182],{"class":77},[71,9278,9279],{"class":81}," 详细日志，-v \u002F -vv \u002F -vvv 可叠加\n",[71,9281,9282,9284,9286,9288,9290,9292,9294,9296,9299,9301,9303,9305,9308,9310,9313],{"class":73,"line":719},[71,9283,898],{"class":445},[71,9285,9192],{"class":472},[71,9287,475],{"class":445},[71,9289,9197],{"class":472},[71,9291,452],{"class":445},[71,9293,9202],{"class":472},[71,9295,452],{"class":445},[71,9297,9298],{"class":472}," action ",[71,9300,909],{"class":99},[71,9302,9090],{"class":472},[71,9304,442],{"class":99},[71,9306,9307],{"class":439},"ArgAction",[71,9309,442],{"class":99},[71,9311,9312],{"class":439},"Count",[71,9314,491],{"class":445},[71,9316,9317,9320,9322,9324],{"class":73,"line":724},[71,9318,9319],{"class":507},"    verbose",[71,9321,511],{"class":99},[71,9323,527],{"class":439},[71,9325,517],{"class":445},[71,9327,9328],{"class":73,"line":732},[71,9329,464],{"emptyLinePlaceholder":463},[71,9331,9332,9334],{"class":73,"line":768},[71,9333,9182],{"class":77},[71,9335,9336],{"class":81}," 并发数，1-32\n",[71,9338,9339,9341,9343,9345,9347,9349,9351,9353,9356,9358,9361,9363,9366,9368,9370,9372,9375,9378,9380,9382,9384,9386,9389,9391,9393,9396,9399],{"class":73,"line":773},[71,9340,898],{"class":445},[71,9342,9192],{"class":472},[71,9344,475],{"class":445},[71,9346,9197],{"class":472},[71,9348,452],{"class":445},[71,9350,9202],{"class":472},[71,9352,452],{"class":445},[71,9354,9355],{"class":472}," default_value_t ",[71,9357,909],{"class":99},[71,9359,9360],{"class":472}," 4",[71,9362,452],{"class":445},[71,9364,9365],{"class":472}," value_parser ",[71,9367,909],{"class":99},[71,9369,9090],{"class":472},[71,9371,442],{"class":99},[71,9373,9374],{"class":472},"value_parser",[71,9376,9377],{"class":99},"!",[71,9379,475],{"class":445},[71,9381,4782],{"class":439},[71,9383,681],{"class":445},[71,9385,626],{"class":99},[71,9387,9388],{"class":472},"range",[71,9390,475],{"class":445},[71,9392,2061],{"class":472},[71,9394,9395],{"class":99},"..=",[71,9397,9398],{"class":472},"32",[71,9400,9401],{"class":445},"))]\n",[71,9403,9404,9407,9409,9411],{"class":73,"line":781},[71,9405,9406],{"class":507},"    jobs",[71,9408,511],{"class":99},[71,9410,527],{"class":439},[71,9412,517],{"class":445},[71,9414,9415],{"class":73,"line":796},[71,9416,464],{"emptyLinePlaceholder":463},[71,9418,9419,9421],{"class":73,"line":828},[71,9420,9182],{"class":77},[71,9422,9423],{"class":81}," 输出格式\n",[71,9425,9426,9428,9430,9432,9435,9437,9440,9442,9444,9446,9449,9451,9453],{"class":73,"line":848},[71,9427,898],{"class":445},[71,9429,9192],{"class":472},[71,9431,475],{"class":445},[71,9433,9434],{"class":472},"long",[71,9436,452],{"class":445},[71,9438,9439],{"class":472}," value_enum",[71,9441,452],{"class":445},[71,9443,9355],{"class":472},[71,9445,909],{"class":99},[71,9447,9448],{"class":439}," Format",[71,9450,442],{"class":99},[71,9452,6544],{"class":439},[71,9454,491],{"class":445},[71,9456,9457,9460,9462,9464],{"class":73,"line":857},[71,9458,9459],{"class":507},"    format",[71,9461,511],{"class":99},[71,9463,9448],{"class":439},[71,9465,517],{"class":445},[71,9467,9468],{"class":73,"line":2022},[71,9469,464],{"emptyLinePlaceholder":463},[71,9471,9472,9474,9476,9478,9481],{"class":73,"line":2027},[71,9473,898],{"class":445},[71,9475,9132],{"class":472},[71,9477,475],{"class":445},[71,9479,9480],{"class":472},"subcommand",[71,9482,491],{"class":445},[71,9484,9485,9488,9490,9493],{"class":73,"line":2035},[71,9486,9487],{"class":507},"    command",[71,9489,511],{"class":99},[71,9491,9492],{"class":439}," Commands",[71,9494,517],{"class":445},[71,9496,9497],{"class":73,"line":2068},[71,9498,559],{"class":445},[71,9500,9501],{"class":73,"line":2095},[71,9502,464],{"emptyLinePlaceholder":463},[71,9504,9505,9507,9509,9511,9513,9515,9517],{"class":73,"line":2131},[71,9506,469],{"class":445},[71,9508,176],{"class":472},[71,9510,475],{"class":445},[71,9512,7013],{"class":439},[71,9514,452],{"class":445},[71,9516,9107],{"class":439},[71,9518,491],{"class":445},[71,9520,9521,9523,9525,9527,9529,9531,9534,9536,9539],{"class":73,"line":2136},[71,9522,8048],{"class":99},[71,9524,9448],{"class":439},[71,9526,615],{"class":445},[71,9528,6345],{"class":439},[71,9530,452],{"class":445},[71,9532,9533],{"class":439}," Text",[71,9535,452],{"class":445},[71,9537,9538],{"class":439}," Csv",[71,9540,1180],{"class":445},[71,9542,9543],{"class":73,"line":2143},[71,9544,464],{"emptyLinePlaceholder":463},[71,9546,9547,9549,9551,9553,9556],{"class":73,"line":4626},[71,9548,469],{"class":445},[71,9550,176],{"class":472},[71,9552,475],{"class":445},[71,9554,9555],{"class":439},"Subcommand",[71,9557,491],{"class":445},[71,9559,9560,9562,9564],{"class":73,"line":4649},[71,9561,8048],{"class":99},[71,9563,9492],{"class":439},[71,9565,502],{"class":445},[71,9567,9568,9570],{"class":73,"line":4654},[71,9569,9182],{"class":77},[71,9571,9572],{"class":81}," 处理文件\n",[71,9574,9575,9578],{"class":73,"line":4662},[71,9576,9577],{"class":439},"    Process",[71,9579,502],{"class":445},[71,9581,9582,9585],{"class":73,"line":4698},[71,9583,9584],{"class":77},"        \u002F\u002F\u002F",[71,9586,9587],{"class":81}," 是否强制覆盖\n",[71,9589,9590,9593,9595,9597,9599,9601,9603],{"class":73,"line":4723},[71,9591,9592],{"class":445},"        #[",[71,9594,9192],{"class":472},[71,9596,475],{"class":445},[71,9598,9197],{"class":472},[71,9600,452],{"class":445},[71,9602,9202],{"class":472},[71,9604,491],{"class":445},[71,9606,9607,9610,9612,9615],{"class":73,"line":4741},[71,9608,9609],{"class":507},"        force",[71,9611,511],{"class":99},[71,9613,9614],{"class":439}," bool",[71,9616,517],{"class":445},[71,9618,9619],{"class":73,"line":4763},[71,9620,9621],{"class":445},"    },\n",[71,9623,9624,9626],{"class":73,"line":4787},[71,9625,9182],{"class":77},[71,9627,9628],{"class":81}," 清理缓存（位置参数：直接写在命令后面，不需要 --）\n",[71,9630,9631,9634],{"class":73,"line":7646},[71,9632,9633],{"class":439},"    Clean",[71,9635,502],{"class":445},[71,9637,9638,9641,9643,9645],{"class":73,"line":7659},[71,9639,9640],{"class":507},"        target",[71,9642,511],{"class":99},[71,9644,514],{"class":439},[71,9646,517],{"class":445},[71,9648,9649],{"class":73,"line":7672},[71,9650,9621],{"class":445},[71,9652,9653],{"class":73,"line":7701},[71,9654,559],{"class":445},[71,9656,9657],{"class":73,"line":7719},[71,9658,464],{"emptyLinePlaceholder":463},[71,9660,9661,9663,9665,9667],{"class":73,"line":7725},[71,9662,568],{"class":99},[71,9664,571],{"class":88},[71,9666,574],{"class":445},[71,9668,502],{"class":445},[71,9670,9671,9673,9676,9678,9680,9682,9685,9687,9689],{"class":73,"line":7730},[71,9672,604],{"class":99},[71,9674,9675],{"class":507}," cli",[71,9677,610],{"class":99},[71,9679,9175],{"class":439},[71,9681,442],{"class":99},[71,9683,9684],{"class":88},"parse",[71,9686,4305],{"class":445},[71,9688,551],{"class":77},[71,9690,9691],{"class":81}," 解析失败（缺参数\u002F类型错）自动打印错误和用法，退出码 2\n",[71,9693,9694],{"class":73,"line":7747},[71,9695,464],{"emptyLinePlaceholder":463},[71,9697,9698,9700,9702,9704,9707],{"class":73,"line":7783},[71,9699,2531],{"class":99},[71,9701,9675],{"class":507},[71,9703,626],{"class":99},[71,9705,9706],{"class":472},"command ",[71,9708,6859],{"class":445},[71,9710,9711,9714,9716,9719,9721,9724,9726,9728],{"class":73,"line":7805},[71,9712,9713],{"class":439},"        Commands",[71,9715,442],{"class":99},[71,9717,9718],{"class":439},"Process",[71,9720,615],{"class":445},[71,9722,9723],{"class":507}," force",[71,9725,6465],{"class":445},[71,9727,2563],{"class":99},[71,9729,502],{"class":445},[71,9731,9732,9735,9737,9740,9742,9744,9746,9749,9751,9754,9756,9759,9761,9764,9766,9768,9770,9772],{"class":73,"line":7828},[71,9733,9734],{"class":88},"            println!",[71,9736,475],{"class":445},[71,9738,9739],{"class":92},"\"处理 ",[71,9741,741],{"class":445},[71,9743,8635],{"class":92},[71,9745,741],{"class":445},[71,9747,9748],{"class":92}," (force=",[71,9750,446],{"class":445},[71,9752,9753],{"class":92},"force",[71,9755,706],{"class":445},[71,9757,9758],{"class":92},", jobs=",[71,9760,741],{"class":445},[71,9762,9763],{"class":92},", format=",[71,9765,446],{"class":445},[71,9767,2107],{"class":92},[71,9769,706],{"class":445},[71,9771,8648],{"class":92},[71,9773,517],{"class":445},[71,9775,9776,9779,9781,9784,9786,9788,9790,9793,9795,9797,9799,9802,9804,9806,9808,9811],{"class":73,"line":7851},[71,9777,9778],{"class":507},"                     cli",[71,9780,626],{"class":99},[71,9782,9783],{"class":472},"input",[71,9785,452],{"class":445},[71,9787,9675],{"class":507},[71,9789,626],{"class":99},[71,9791,9792],{"class":472},"output",[71,9794,452],{"class":445},[71,9796,9675],{"class":507},[71,9798,626],{"class":99},[71,9800,9801],{"class":472},"jobs",[71,9803,452],{"class":445},[71,9805,9675],{"class":507},[71,9807,626],{"class":99},[71,9809,9810],{"class":472},"format",[71,9812,765],{"class":445},[71,9814,9815],{"class":73,"line":7871},[71,9816,4476],{"class":445},[71,9818,9819,9821,9823,9826,9828,9831,9833,9835,9837,9839,9842,9844,9847,9849,9851],{"class":73,"line":7876},[71,9820,9713],{"class":439},[71,9822,442],{"class":99},[71,9824,9825],{"class":439},"Clean",[71,9827,615],{"class":445},[71,9829,9830],{"class":507}," target",[71,9832,6465],{"class":445},[71,9834,2563],{"class":99},[71,9836,2566],{"class":88},[71,9838,475],{"class":445},[71,9840,9841],{"class":92},"\"清理 ",[71,9843,446],{"class":445},[71,9845,9846],{"class":92},"target",[71,9848,706],{"class":445},[71,9850,698],{"class":92},[71,9852,2269],{"class":445},[71,9854,9855],{"class":73,"line":7911},[71,9856,1935],{"class":445},[71,9858,9859],{"class":73,"line":7936},[71,9860,559],{"class":445},[14,9862,9863],{},"运行效果：",[62,9865,9867],{"className":64,"code":9866,"language":66,"meta":67,"style":67},"$ mytool --help          # 自动生成的漂亮帮助\n$ mytool -i a.txt process --force\n$ mytool -i a.txt clean cache_dir\n",[52,9868,9869,9886,9904],{"__ignoreMap":67},[71,9870,9871,9874,9877,9880,9883],{"class":73,"line":74},[71,9872,9873],{"class":88},"$",[71,9875,9876],{"class":92}," mytool",[71,9878,9879],{"class":99}," --help",[71,9881,9882],{"class":77},"          #",[71,9884,9885],{"class":81}," 自动生成的漂亮帮助\n",[71,9887,9888,9890,9892,9895,9898,9901],{"class":73,"line":85},[71,9889,9873],{"class":88},[71,9891,9876],{"class":92},[71,9893,9894],{"class":99}," -i",[71,9896,9897],{"class":92}," a.txt",[71,9899,9900],{"class":92}," process",[71,9902,9903],{"class":99}," --force\n",[71,9905,9906,9908,9910,9912,9914,9917],{"class":73,"line":106},[71,9907,9873],{"class":88},[71,9909,9876],{"class":92},[71,9911,9894],{"class":99},[71,9913,9897],{"class":92},[71,9915,9916],{"class":92}," clean",[71,9918,9919],{"class":92}," cache_dir\n",[57,9921,9923],{"id":9922},"_63-常用-arg-属性速查","6.3 常用 arg 属性速查",[1565,9925,9926,9939],{},[1568,9927,9928],{},[1571,9929,9930,9933,9936],{},[1574,9931,9932],{},"属性",[1574,9934,9935],{},"作用",[1574,9937,9938],{},"例",[1584,9940,9941,9961,9978,9991,10008,10023,10036,10059],{},[1571,9942,9943,9948,9956],{},[1589,9944,9945],{},[52,9946,9947],{},"short, long",[1589,9949,9950,2687,9953],{},[52,9951,9952],{},"-x",[52,9954,9955],{},"--xxx",[1589,9957,9958],{},[52,9959,9960],{},"#[arg(short, long)]",[1571,9962,9963,9968,9971],{},[1589,9964,9965],{},[52,9966,9967],{},"default_value = \"...\"",[1589,9969,9970],{},"字符串默认值",[1589,9972,9973,9974,9977],{},"适合所有 ",[52,9975,9976],{},"FromStr"," 类型",[1571,9979,9980,9985,9988],{},[1589,9981,9982],{},[52,9983,9984],{},"default_value_t = 4",[1589,9986,9987],{},"类型化默认值",[1589,9989,9990],{},"数字、枚举用它可以出现在 help 里",[1571,9992,9993,9998,10005],{},[1589,9994,9995],{},[52,9996,9997],{},"env = \"API_KEY\"",[1589,9999,10000,10001,10004],{},"从环境变量读（需 feature ",[52,10002,10003],{},"env","）",[1589,10006,10007],{},"密钥不入命令行历史",[1571,10009,10010,10015,10018],{},[1589,10011,10012],{},[52,10013,10014],{},"value_parser = ...",[1589,10016,10017],{},"自定义校验\u002F转换",[1589,10019,10020],{},[52,10021,10022],{},"value_parser!(u16).range(1..)",[1571,10024,10025,10030,10033],{},[1589,10026,10027],{},[52,10028,10029],{},"required = true",[1589,10031,10032],{},"必填（Option 字段天然可选，无需此项）",[1589,10034,10035],{},"位置参数默认必填",[1571,10037,10038,10046,10049],{},[1589,10039,10040,2687,10043],{},[52,10041,10042],{},"conflicts_with",[52,10044,10045],{},"requires",[1589,10047,10048],{},"参数间约束",[1589,10050,10051,10054,10055,10058],{},[52,10052,10053],{},"--json"," 与 ",[52,10056,10057],{},"--yaml"," 互斥",[1571,10060,10061,10066,10069],{},[1589,10062,10063],{},[52,10064,10065],{},"hide = true",[1589,10067,10068],{},"不在 help 中显示",[1589,10070,10071],{},"内部调试参数",[23,10073],{},[26,10075,10077],{"id":10076},"第-7-章-log-env_logger-tracing日志","第 7 章 log \u002F env_logger \u002F tracing：日志",[57,10079,10081],{"id":10080},"_71-它解决什么问题","7.1 它解决什么问题",[14,10083,10084,10087,10088,10094,10095,10098],{},[52,10085,10086],{},"println!"," 无法控制级别、无法关闭、无法加时间戳和模块名。Rust 日志生态分两层：",[18,10089,10090,10093],{},[52,10091,10092],{},"log"," 是门面（facade）","——库代码只调 log 的宏，不关心日志去哪；",[18,10096,10097],{},"env_logger \u002F tracing-subscriber 是实现（backend）","——由最终的应用决定日志格式和去向。",[14,10100,10101,10104],{},[18,10102,10103],{},"为什么库必须只用 log 门面？"," 如果每个库自己初始化输出，日志格式就乱了，而且库不应该替应用做决定。这个「门面 + 实现」分层让任何库的日志都能被应用统一接管。",[57,10106,10108],{"id":10107},"_72-log-env_logger简单够用的方案","7.2 log + env_logger：简单够用的方案",[62,10110,10112],{"className":143,"code":10111,"language":145,"meta":67,"style":67},"[dependencies]\nlog = \"0.4\"\nenv_logger = \"0.11\"\n",[52,10113,10114,10118,10123],{"__ignoreMap":67},[71,10115,10116],{"class":73,"line":74},[71,10117,152],{},[71,10119,10120],{"class":73,"line":85},[71,10121,10122],{},"log = \"0.4\"\n",[71,10124,10125],{"class":73,"line":106},[71,10126,10127],{},"env_logger = \"0.11\"\n",[62,10129,10131],{"className":427,"code":10130,"language":429,"meta":67,"style":67},"use log::{trace, debug, info, warn, error};\n\nfn main() {\n    \u002F\u002F 只在应用的 main 里初始化一次；库代码里绝不要调用 init\n    \u002F\u002F 默认级别由环境变量 RUST_LOG 控制，无需改代码\n    env_logger::init();\n\n    error!(\"出错了\");   \u002F\u002F 默认只显示 warn 及以上\n    warn!(\"警告\");\n    info!(\"普通信息\");  \u002F\u002F RUST_LOG=info 才显示\n    debug!(\"调试细节\"); \u002F\u002F RUST_LOG=debug\n    trace!(\"最啰嗦\");   \u002F\u002F RUST_LOG=trace\n}\n",[52,10132,10133,10169,10173,10183,10190,10197,10209,10213,10230,10242,10259,10276,10293],{"__ignoreMap":67},[71,10134,10135,10137,10140,10142,10144,10147,10149,10152,10154,10157,10159,10162,10164,10167],{"class":73,"line":74},[71,10136,436],{"class":99},[71,10138,10139],{"class":439}," log",[71,10141,442],{"class":99},[71,10143,446],{"class":445},[71,10145,10146],{"class":472},"trace",[71,10148,452],{"class":445},[71,10150,10151],{"class":472}," debug",[71,10153,452],{"class":445},[71,10155,10156],{"class":472}," info",[71,10158,452],{"class":445},[71,10160,10161],{"class":472}," warn",[71,10163,452],{"class":445},[71,10165,10166],{"class":472}," error",[71,10168,458],{"class":445},[71,10170,10171],{"class":73,"line":85},[71,10172,464],{"emptyLinePlaceholder":463},[71,10174,10175,10177,10179,10181],{"class":73,"line":106},[71,10176,568],{"class":99},[71,10178,571],{"class":88},[71,10180,574],{"class":445},[71,10182,502],{"class":445},[71,10184,10185,10187],{"class":73,"line":121},[71,10186,595],{"class":77},[71,10188,10189],{"class":81}," 只在应用的 main 里初始化一次；库代码里绝不要调用 init\n",[71,10191,10192,10194],{"class":73,"line":259},[71,10193,595],{"class":77},[71,10195,10196],{"class":81}," 默认级别由环境变量 RUST_LOG 控制，无需改代码\n",[71,10198,10199,10202,10204,10207],{"class":73,"line":273},[71,10200,10201],{"class":439},"    env_logger",[71,10203,442],{"class":99},[71,10205,10206],{"class":88},"init",[71,10208,1499],{"class":445},[71,10210,10211],{"class":73,"line":286},[71,10212,464],{"emptyLinePlaceholder":463},[71,10214,10215,10218,10220,10223,10225,10227],{"class":73,"line":300},[71,10216,10217],{"class":88},"    error!",[71,10219,475],{"class":445},[71,10221,10222],{"class":92},"\"出错了\"",[71,10224,711],{"class":445},[71,10226,1886],{"class":77},[71,10228,10229],{"class":81}," 默认只显示 warn 及以上\n",[71,10231,10232,10235,10237,10240],{"class":73,"line":316},[71,10233,10234],{"class":88},"    warn!",[71,10236,475],{"class":445},[71,10238,10239],{"class":92},"\"警告\"",[71,10241,765],{"class":445},[71,10243,10244,10247,10249,10252,10254,10256],{"class":73,"line":330},[71,10245,10246],{"class":88},"    info!",[71,10248,475],{"class":445},[71,10250,10251],{"class":92},"\"普通信息\"",[71,10253,711],{"class":445},[71,10255,3836],{"class":77},[71,10257,10258],{"class":81}," RUST_LOG=info 才显示\n",[71,10260,10261,10264,10266,10269,10271,10273],{"class":73,"line":352},[71,10262,10263],{"class":88},"    debug!",[71,10265,475],{"class":445},[71,10267,10268],{"class":92},"\"调试细节\"",[71,10270,711],{"class":445},[71,10272,551],{"class":77},[71,10274,10275],{"class":81}," RUST_LOG=debug\n",[71,10277,10278,10281,10283,10286,10288,10290],{"class":73,"line":601},[71,10279,10280],{"class":88},"    trace!",[71,10282,475],{"class":445},[71,10284,10285],{"class":92},"\"最啰嗦\"",[71,10287,711],{"class":445},[71,10289,1886],{"class":77},[71,10291,10292],{"class":81}," RUST_LOG=trace\n",[71,10294,10295],{"class":73,"line":656},[71,10296,559],{"class":445},[62,10298,10300],{"className":64,"code":10299,"language":66,"meta":67,"style":67},"RUST_LOG=info cargo run                  # 全局 info 级\nRUST_LOG=myapp=debug,reqwest=warn cargo run  # 按模块精细控制：自己代码 debug，第三方库只留 warn\n",[52,10301,10302,10322],{"__ignoreMap":67},[71,10303,10304,10307,10309,10312,10314,10316,10319],{"class":73,"line":74},[71,10305,10306],{"class":507},"RUST_LOG",[71,10308,909],{"class":99},[71,10310,10311],{"class":92},"info",[71,10313,340],{"class":88},[71,10315,264],{"class":92},[71,10317,10318],{"class":77},"                  #",[71,10320,10321],{"class":81}," 全局 info 级\n",[71,10323,10324,10326,10328,10331,10333,10336,10338,10340,10343,10345,10347,10349],{"class":73,"line":85},[71,10325,10306],{"class":507},[71,10327,909],{"class":99},[71,10329,10330],{"class":507},"myapp",[71,10332,909],{"class":99},[71,10334,10335],{"class":92},"debug,",[71,10337,5744],{"class":507},[71,10339,909],{"class":99},[71,10341,10342],{"class":92},"warn",[71,10344,340],{"class":88},[71,10346,264],{"class":92},[71,10348,225],{"class":77},[71,10350,10351],{"class":81}," 按模块精细控制：自己代码 debug，第三方库只留 warn\n",[14,10353,10354,10357,10358,10361],{},[18,10355,10356],{},"为什么要按模块控制级别？"," 开全局 debug 时，tokio\u002Fhyper 等底层库的海量日志会淹没你自己的日志。",[52,10359,10360],{},"myapp=debug,reqwest=warn"," 这种过滤让你只看想看的。",[57,10363,10365],{"id":10364},"_73-tracing结构化日志-调用链服务端推荐","7.3 tracing：结构化日志 + 调用链（服务端推荐）",[14,10367,10368,10369,10372,10373,10375],{},"log 记录的是一条条孤立的字符串；tracing 记录的是",[18,10370,10371],{},"带结构化字段的事件","，外加 ",[52,10374,71],{}," 表达「这堆日志属于哪个请求」——异步服务端排查问题的神器。",[62,10377,10379],{"className":143,"code":10378,"language":145,"meta":67,"style":67},"[dependencies]\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\", \"json\"] }\n",[52,10380,10381,10385,10390],{"__ignoreMap":67},[71,10382,10383],{"class":73,"line":74},[71,10384,152],{},[71,10386,10387],{"class":73,"line":85},[71,10388,10389],{},"tracing = \"0.1\"\n",[71,10391,10392],{"class":73,"line":106},[71,10393,10394],{},"tracing-subscriber = { version = \"0.3\", features = [\"env-filter\", \"json\"] }\n",[62,10396,10398],{"className":427,"code":10397,"language":429,"meta":67,"style":67},"use tracing::{info, warn, instrument, info_span};\n\n\u002F\u002F #[instrument] 自动给函数建 span，参数自动记录为字段\n\u002F\u002F 这个函数内的所有日志都会自动带上 request_id 和 user 字段——无需手动传递！\n#[instrument(skip(db), fields(request_id = %uuid_v4()))]\nasync fn handle_request(user: &str, db: &Database) {\n    info!(\"开始处理\");\n    step1().await;\n    warn!(latency_ms = 120, \"某步骤偏慢\"); \u002F\u002F 结构化字段：可以被日志系统检索、聚合\n}\n\n#[instrument]\nasync fn step1() {\n    info!(\"步骤1\"); \u002F\u002F 自动带上父 span 的 request_id、user\n}\n\nstruct Database;\nfn uuid_v4() -> String { \"req-abc123\".into() }\n\n#[tokio::main]\nasync fn main() {\n    tracing_subscriber::fmt()\n        .with_env_filter( \u002F\u002F 同样支持 RUST_LOG 环境变量过滤\n            tracing_subscriber::EnvFilter::try_from_default_env()\n                .unwrap_or_else(|_| \"myapp=info\".into()))\n        \u002F\u002F .json()      \u002F\u002F 生产环境开 JSON 输出，对接 ELK\u002FLoki 等日志系统\n        .init();\n\n    let db = Database;\n    handle_request(\"alice\", &db).await;\n}\n",[52,10399,10400,10429,10433,10440,10447,10485,10521,10532,10543,10569,10573,10577,10585,10598,10614,10618,10622,10631,10657,10661,10673,10685,10697,10711,10728,10753,10760,10768,10772,10784,10806],{"__ignoreMap":67},[71,10401,10402,10404,10407,10409,10411,10413,10415,10417,10419,10422,10424,10427],{"class":73,"line":74},[71,10403,436],{"class":99},[71,10405,10406],{"class":439}," tracing",[71,10408,442],{"class":99},[71,10410,446],{"class":445},[71,10412,10311],{"class":472},[71,10414,452],{"class":445},[71,10416,10161],{"class":472},[71,10418,452],{"class":445},[71,10420,10421],{"class":472}," instrument",[71,10423,452],{"class":445},[71,10425,10426],{"class":472}," info_span",[71,10428,458],{"class":445},[71,10430,10431],{"class":73,"line":85},[71,10432,464],{"emptyLinePlaceholder":463},[71,10434,10435,10437],{"class":73,"line":106},[71,10436,998],{"class":77},[71,10438,10439],{"class":81}," #[instrument] 自动给函数建 span，参数自动记录为字段\n",[71,10441,10442,10444],{"class":73,"line":121},[71,10443,998],{"class":77},[71,10445,10446],{"class":81}," 这个函数内的所有日志都会自动带上 request_id 和 user 字段——无需手动传递！\n",[71,10448,10449,10451,10454,10456,10459,10461,10464,10466,10469,10471,10474,10476,10479,10482],{"class":73,"line":259},[71,10450,469],{"class":445},[71,10452,10453],{"class":472},"instrument",[71,10455,475],{"class":445},[71,10457,10458],{"class":472},"skip",[71,10460,475],{"class":445},[71,10462,10463],{"class":472},"db",[71,10465,2398],{"class":445},[71,10467,10468],{"class":472}," fields",[71,10470,475],{"class":445},[71,10472,10473],{"class":472},"request_id ",[71,10475,909],{"class":99},[71,10477,10478],{"class":99}," %",[71,10480,10481],{"class":472},"uuid_v4",[71,10483,10484],{"class":445},"()))]\n",[71,10486,10487,10489,10491,10494,10496,10499,10501,10503,10505,10507,10510,10512,10514,10517,10519],{"class":73,"line":273},[71,10488,2790],{"class":99},[71,10490,2793],{"class":99},[71,10492,10493],{"class":88}," handle_request",[71,10495,475],{"class":445},[71,10497,10498],{"class":507},"user",[71,10500,511],{"class":99},[71,10502,1714],{"class":99},[71,10504,1717],{"class":439},[71,10506,452],{"class":445},[71,10508,10509],{"class":507}," db",[71,10511,511],{"class":99},[71,10513,1714],{"class":99},[71,10515,10516],{"class":439},"Database",[71,10518,681],{"class":445},[71,10520,502],{"class":445},[71,10522,10523,10525,10527,10530],{"class":73,"line":286},[71,10524,10246],{"class":88},[71,10526,475],{"class":445},[71,10528,10529],{"class":92},"\"开始处理\"",[71,10531,765],{"class":445},[71,10533,10534,10537,10539,10541],{"class":73,"line":300},[71,10535,10536],{"class":88},"    step1",[71,10538,574],{"class":445},[71,10540,2878],{"class":99},[71,10542,687],{"class":445},[71,10544,10545,10547,10549,10552,10554,10557,10559,10562,10564,10566],{"class":73,"line":316},[71,10546,10234],{"class":88},[71,10548,475],{"class":445},[71,10550,10551],{"class":507},"latency_ms",[71,10553,610],{"class":99},[71,10555,10556],{"class":88}," 120",[71,10558,452],{"class":445},[71,10560,10561],{"class":92}," \"某步骤偏慢\"",[71,10563,711],{"class":445},[71,10565,551],{"class":77},[71,10567,10568],{"class":81}," 结构化字段：可以被日志系统检索、聚合\n",[71,10570,10571],{"class":73,"line":330},[71,10572,559],{"class":445},[71,10574,10575],{"class":73,"line":352},[71,10576,464],{"emptyLinePlaceholder":463},[71,10578,10579,10581,10583],{"class":73,"line":601},[71,10580,469],{"class":445},[71,10582,10453],{"class":472},[71,10584,2785],{"class":445},[71,10586,10587,10589,10591,10594,10596],{"class":73,"line":656},[71,10588,2790],{"class":99},[71,10590,2793],{"class":99},[71,10592,10593],{"class":88}," step1",[71,10595,574],{"class":445},[71,10597,502],{"class":445},[71,10599,10600,10602,10604,10607,10609,10611],{"class":73,"line":690},[71,10601,10246],{"class":88},[71,10603,475],{"class":445},[71,10605,10606],{"class":92},"\"步骤1\"",[71,10608,711],{"class":445},[71,10610,551],{"class":77},[71,10612,10613],{"class":81}," 自动带上父 span 的 request_id、user\n",[71,10615,10616],{"class":73,"line":719},[71,10617,559],{"class":445},[71,10619,10620],{"class":73,"line":724},[71,10621,464],{"emptyLinePlaceholder":463},[71,10623,10624,10626,10629],{"class":73,"line":732},[71,10625,496],{"class":99},[71,10627,10628],{"class":439}," Database",[71,10630,687],{"class":445},[71,10632,10633,10635,10638,10640,10642,10644,10646,10649,10651,10653,10655],{"class":73,"line":768},[71,10634,568],{"class":99},[71,10636,10637],{"class":88}," uuid_v4",[71,10639,574],{"class":445},[71,10641,577],{"class":99},[71,10643,514],{"class":439},[71,10645,615],{"class":445},[71,10647,10648],{"class":92}," \"req-abc123\"",[71,10650,626],{"class":99},[71,10652,629],{"class":88},[71,10654,574],{"class":445},[71,10656,1180],{"class":445},[71,10658,10659],{"class":73,"line":773},[71,10660,464],{"emptyLinePlaceholder":463},[71,10662,10663,10665,10667,10669,10671],{"class":73,"line":781},[71,10664,469],{"class":445},[71,10666,2777],{"class":472},[71,10668,442],{"class":99},[71,10670,2782],{"class":472},[71,10672,2785],{"class":445},[71,10674,10675,10677,10679,10681,10683],{"class":73,"line":796},[71,10676,2790],{"class":99},[71,10678,2793],{"class":99},[71,10680,571],{"class":88},[71,10682,574],{"class":445},[71,10684,502],{"class":445},[71,10686,10687,10690,10692,10695],{"class":73,"line":828},[71,10688,10689],{"class":439},"    tracing_subscriber",[71,10691,442],{"class":99},[71,10693,10694],{"class":88},"fmt",[71,10696,3983],{"class":445},[71,10698,10699,10701,10704,10706,10708],{"class":73,"line":848},[71,10700,1775],{"class":99},[71,10702,10703],{"class":88},"with_env_filter",[71,10705,475],{"class":445},[71,10707,551],{"class":77},[71,10709,10710],{"class":81}," 同样支持 RUST_LOG 环境变量过滤\n",[71,10712,10713,10716,10718,10721,10723,10726],{"class":73,"line":857},[71,10714,10715],{"class":439},"            tracing_subscriber",[71,10717,442],{"class":99},[71,10719,10720],{"class":439},"EnvFilter",[71,10722,442],{"class":99},[71,10724,10725],{"class":88},"try_from_default_env",[71,10727,3983],{"class":445},[71,10729,10730,10733,10736,10738,10740,10742,10744,10747,10749,10751],{"class":73,"line":2022},[71,10731,10732],{"class":99},"                .",[71,10734,10735],{"class":88},"unwrap_or_else",[71,10737,475],{"class":445},[71,10739,7400],{"class":99},[71,10741,3584],{"class":507},[71,10743,7400],{"class":99},[71,10745,10746],{"class":92}," \"myapp=info\"",[71,10748,626],{"class":99},[71,10750,629],{"class":88},[71,10752,8436],{"class":445},[71,10754,10755,10757],{"class":73,"line":2027},[71,10756,2825],{"class":77},[71,10758,10759],{"class":81}," .json()      \u002F\u002F 生产环境开 JSON 输出，对接 ELK\u002FLoki 等日志系统\n",[71,10761,10762,10764,10766],{"class":73,"line":2035},[71,10763,1775],{"class":99},[71,10765,10206],{"class":88},[71,10767,1499],{"class":445},[71,10769,10770],{"class":73,"line":2068},[71,10771,464],{"emptyLinePlaceholder":463},[71,10773,10774,10776,10778,10780,10782],{"class":73,"line":2095},[71,10775,604],{"class":99},[71,10777,10509],{"class":507},[71,10779,610],{"class":99},[71,10781,10628],{"class":439},[71,10783,687],{"class":445},[71,10785,10786,10789,10791,10794,10796,10798,10800,10802,10804],{"class":73,"line":2131},[71,10787,10788],{"class":88},"    handle_request",[71,10790,475],{"class":445},[71,10792,10793],{"class":92},"\"alice\"",[71,10795,452],{"class":445},[71,10797,1714],{"class":99},[71,10799,10463],{"class":507},[71,10801,681],{"class":445},[71,10803,2878],{"class":99},[71,10805,687],{"class":445},[71,10807,10808],{"class":73,"line":2136},[71,10809,559],{"class":445},[14,10811,10812],{},[18,10813,10814],{},"输出（文本模式）：",[62,10816,10820],{"className":10817,"code":10819,"language":5030,"meta":67},[10818],"language-text","INFO handle_request{request_id=\"req-abc123\" user=\"alice\"}: 开始处理\nINFO handle_request{request_id=\"req-abc123\" user=\"alice\"}:step1: 步骤1\n",[52,10821,10819],{"__ignoreMap":67},[14,10823,10824,10827,10828,10830,10831,10834],{},[18,10825,10826],{},"为什么线上要 JSON 输出？"," 文本日志只能靠 grep；JSON 日志里 ",[52,10829,10551],{},"、",[52,10832,10833],{},"request_id"," 是可索引字段，可以在日志平台做「查 request_id=xxx 的全部日志」「统计 latency_ms 的 P99」这类查询。",[11,10836,10837],{},[14,10838,10839,10840,10849,10850,10853],{},"建议：",[18,10841,10842,10843,10845,10846],{},"写库用 ",[52,10844,10092],{},"，写服务端应用用 ",[52,10847,10848],{},"tracing","。tracing 兼容 log 的宏（加 ",[52,10851,10852],{},"tracing-log"," 桥），迁移成本低。",[23,10855],{},[26,10857,10859],{"id":10858},"第-8-章-chrono日期与时间","第 8 章 chrono：日期与时间",[57,10861,10863],{"id":10862},"_81-它解决什么问题","8.1 它解决什么问题",[14,10865,10866,10869],{},[52,10867,10868],{},"std::time::SystemTime"," 只给时间戳，不管时区、格式化、日期运算。chrono 是生态标准的日期时间库。",[62,10871,10873],{"className":143,"code":10872,"language":145,"meta":67,"style":67},"[dependencies]\nchrono = { version = \"0.4\", features = [\"serde\"] } # serde feature 让时间字段可直接序列化\n",[52,10874,10875,10879],{"__ignoreMap":67},[71,10876,10877],{"class":73,"line":74},[71,10878,152],{},[71,10880,10881],{"class":73,"line":85},[71,10882,10883],{},"chrono = { version = \"0.4\", features = [\"serde\"] } # serde feature 让时间字段可直接序列化\n",[57,10885,10887],{"id":10886},"_82-常用操作","8.2 常用操作",[62,10889,10891],{"className":427,"code":10890,"language":429,"meta":67,"style":67},"use chrono::{Local, Utc, NaiveDate, DateTime, Duration, TimeZone, Datelike, Timelike};\n\nfn main() {\n    \u002F\u002F 1) 获取当前时间\n    let now_utc = Utc::now();       \u002F\u002F DateTime\u003CUtc>：服务器存时间永远用 UTC\n    let now_local = Local::now();   \u002F\u002F DateTime\u003CLocal>：只用于展示给用户\n    println!(\"UTC:   {now_utc}\");\n    println!(\"本地:  {now_local}\");\n\n    \u002F\u002F 2) 格式化与解析\n    println!(\"{}\", now_utc.format(\"%Y-%m-%d %H:%M:%S\"));          \u002F\u002F 自定义格式\n    println!(\"{}\", now_utc.to_rfc3339());                          \u002F\u002F 2026-09-28T12:00:00+00:00，API 交换标准格式\n    let dt = DateTime::parse_from_rfc3339(\"2026-09-28T12:00:00+08:00\").unwrap();\n    let utc_dt = dt.with_timezone(&Utc);                           \u002F\u002F 时区转换是零成本的（内部都是时间戳）\n\n    \u002F\u002F 3) 从日期构造时间\n    let d = NaiveDate::from_ymd_opt(2026, 2, 29)   \u002F\u002F Opt 版本返回 Option，不 panic\n        .expect(\"2026 不是闰年，这里会 panic\");\n    let dt2 = Utc.from_utc_datetime(&d.and_hms_opt(8, 30, 0).unwrap());\n\n    \u002F\u002F 4) 时间运算\n    let tomorrow = Utc::now() + Duration::days(1);\n    let deadline = Utc::now() + Duration::hours(3) + Duration::minutes(30);\n    let diff = deadline - Utc::now();       \u002F\u002F 减法得 Duration\n    println!(\"还剩 {} 秒\", diff.num_seconds());\n    let _ = (d, dt2, tomorrow);\n\n    \u002F\u002F 5) 取字段\n    println!(\"{}年{}月{}日 星期{:?}\", now_local.year(), now_local.month(), now_local.day(), now_local.weekday());\n}\n",[52,10892,10893,10943,10947,10957,10964,10986,11009,11029,11049,11053,11060,11092,11121,11150,11181,11185,11192,11230,11244,11294,11298,11305,11337,11385,11412,11437,11459,11463,11470,11539],{"__ignoreMap":67},[71,10894,10895,10897,10900,10902,10904,10907,10909,10912,10914,10917,10919,10922,10924,10926,10928,10931,10933,10936,10938,10941],{"class":73,"line":74},[71,10896,436],{"class":99},[71,10898,10899],{"class":439}," chrono",[71,10901,442],{"class":99},[71,10903,446],{"class":445},[71,10905,10906],{"class":439},"Local",[71,10908,452],{"class":445},[71,10910,10911],{"class":439}," Utc",[71,10913,452],{"class":445},[71,10915,10916],{"class":439}," NaiveDate",[71,10918,452],{"class":445},[71,10920,10921],{"class":439}," DateTime",[71,10923,452],{"class":445},[71,10925,2972],{"class":439},[71,10927,452],{"class":445},[71,10929,10930],{"class":439}," TimeZone",[71,10932,452],{"class":445},[71,10934,10935],{"class":439}," Datelike",[71,10937,452],{"class":445},[71,10939,10940],{"class":439}," Timelike",[71,10942,458],{"class":445},[71,10944,10945],{"class":73,"line":85},[71,10946,464],{"emptyLinePlaceholder":463},[71,10948,10949,10951,10953,10955],{"class":73,"line":106},[71,10950,568],{"class":99},[71,10952,571],{"class":88},[71,10954,574],{"class":445},[71,10956,502],{"class":445},[71,10958,10959,10961],{"class":73,"line":121},[71,10960,595],{"class":77},[71,10962,10963],{"class":81}," 1) 获取当前时间\n",[71,10965,10966,10968,10971,10973,10975,10977,10979,10981,10983],{"class":73,"line":259},[71,10967,604],{"class":99},[71,10969,10970],{"class":507}," now_utc",[71,10972,610],{"class":99},[71,10974,10911],{"class":439},[71,10976,442],{"class":99},[71,10978,8526],{"class":88},[71,10980,4305],{"class":445},[71,10982,6468],{"class":77},[71,10984,10985],{"class":81}," DateTime\u003CUtc>：服务器存时间永远用 UTC\n",[71,10987,10988,10990,10993,10995,10998,11000,11002,11004,11006],{"class":73,"line":273},[71,10989,604],{"class":99},[71,10991,10992],{"class":507}," now_local",[71,10994,610],{"class":99},[71,10996,10997],{"class":439}," Local",[71,10999,442],{"class":99},[71,11001,8526],{"class":88},[71,11003,4305],{"class":445},[71,11005,1886],{"class":77},[71,11007,11008],{"class":81}," DateTime\u003CLocal>：只用于展示给用户\n",[71,11010,11011,11013,11015,11018,11020,11023,11025,11027],{"class":73,"line":286},[71,11012,693],{"class":88},[71,11014,475],{"class":445},[71,11016,11017],{"class":92},"\"UTC:   ",[71,11019,446],{"class":445},[71,11021,11022],{"class":92},"now_utc",[71,11024,706],{"class":445},[71,11026,698],{"class":92},[71,11028,765],{"class":445},[71,11030,11031,11033,11035,11038,11040,11043,11045,11047],{"class":73,"line":300},[71,11032,693],{"class":88},[71,11034,475],{"class":445},[71,11036,11037],{"class":92},"\"本地:  ",[71,11039,446],{"class":445},[71,11041,11042],{"class":92},"now_local",[71,11044,706],{"class":445},[71,11046,698],{"class":92},[71,11048,765],{"class":445},[71,11050,11051],{"class":73,"line":316},[71,11052,464],{"emptyLinePlaceholder":463},[71,11054,11055,11057],{"class":73,"line":330},[71,11056,595],{"class":77},[71,11058,11059],{"class":81}," 2) 格式化与解析\n",[71,11061,11062,11064,11066,11068,11070,11072,11074,11076,11078,11080,11082,11085,11087,11089],{"class":73,"line":352},[71,11063,693],{"class":88},[71,11065,475],{"class":445},[71,11067,698],{"class":92},[71,11069,741],{"class":445},[71,11071,698],{"class":92},[71,11073,452],{"class":445},[71,11075,10970],{"class":507},[71,11077,626],{"class":99},[71,11079,9810],{"class":88},[71,11081,475],{"class":445},[71,11083,11084],{"class":92},"\"%Y-%m-%d %H:%M:%S\"",[71,11086,2482],{"class":445},[71,11088,1032],{"class":77},[71,11090,11091],{"class":81}," 自定义格式\n",[71,11093,11094,11096,11098,11100,11102,11104,11106,11108,11110,11113,11115,11118],{"class":73,"line":601},[71,11095,693],{"class":88},[71,11097,475],{"class":445},[71,11099,698],{"class":92},[71,11101,741],{"class":445},[71,11103,698],{"class":92},[71,11105,452],{"class":445},[71,11107,10970],{"class":507},[71,11109,626],{"class":99},[71,11111,11112],{"class":88},"to_rfc3339",[71,11114,2123],{"class":445},[71,11116,11117],{"class":77},"                          \u002F\u002F",[71,11119,11120],{"class":81}," 2026-09-28T12:00:00+00:00，API 交换标准格式\n",[71,11122,11123,11125,11128,11130,11132,11134,11137,11139,11142,11144,11146,11148],{"class":73,"line":656},[71,11124,604],{"class":99},[71,11126,11127],{"class":507}," dt",[71,11129,610],{"class":99},[71,11131,10921],{"class":439},[71,11133,442],{"class":99},[71,11135,11136],{"class":88},"parse_from_rfc3339",[71,11138,475],{"class":445},[71,11140,11141],{"class":92},"\"2026-09-28T12:00:00+08:00\"",[71,11143,681],{"class":445},[71,11145,626],{"class":99},[71,11147,1496],{"class":88},[71,11149,1499],{"class":445},[71,11151,11152,11154,11157,11159,11161,11163,11166,11168,11170,11173,11175,11178],{"class":73,"line":690},[71,11153,604],{"class":99},[71,11155,11156],{"class":507}," utc_dt",[71,11158,610],{"class":99},[71,11160,11127],{"class":507},[71,11162,626],{"class":99},[71,11164,11165],{"class":88},"with_timezone",[71,11167,475],{"class":445},[71,11169,675],{"class":99},[71,11171,11172],{"class":439},"Utc",[71,11174,711],{"class":445},[71,11176,11177],{"class":77},"                           \u002F\u002F",[71,11179,11180],{"class":81}," 时区转换是零成本的（内部都是时间戳）\n",[71,11182,11183],{"class":73,"line":719},[71,11184,464],{"emptyLinePlaceholder":463},[71,11186,11187,11189],{"class":73,"line":724},[71,11188,595],{"class":77},[71,11190,11191],{"class":81}," 3) 从日期构造时间\n",[71,11193,11194,11196,11199,11201,11203,11205,11208,11210,11213,11215,11218,11220,11223,11225,11227],{"class":73,"line":732},[71,11195,604],{"class":99},[71,11197,11198],{"class":507}," d",[71,11200,610],{"class":99},[71,11202,10916],{"class":439},[71,11204,442],{"class":99},[71,11206,11207],{"class":88},"from_ymd_opt",[71,11209,475],{"class":445},[71,11211,11212],{"class":88},"2026",[71,11214,452],{"class":445},[71,11216,11217],{"class":88}," 2",[71,11219,452],{"class":445},[71,11221,11222],{"class":88}," 29",[71,11224,681],{"class":445},[71,11226,1886],{"class":77},[71,11228,11229],{"class":81}," Opt 版本返回 Option，不 panic\n",[71,11231,11232,11234,11237,11239,11242],{"class":73,"line":768},[71,11233,1775],{"class":99},[71,11235,11236],{"class":88},"expect",[71,11238,475],{"class":445},[71,11240,11241],{"class":92},"\"2026 不是闰年，这里会 panic\"",[71,11243,765],{"class":445},[71,11245,11246,11248,11251,11253,11255,11257,11260,11262,11264,11267,11269,11272,11274,11277,11279,11282,11284,11286,11288,11290,11292],{"class":73,"line":773},[71,11247,604],{"class":99},[71,11249,11250],{"class":507}," dt2",[71,11252,610],{"class":99},[71,11254,10911],{"class":439},[71,11256,626],{"class":99},[71,11258,11259],{"class":88},"from_utc_datetime",[71,11261,475],{"class":445},[71,11263,675],{"class":99},[71,11265,11266],{"class":507},"d",[71,11268,626],{"class":99},[71,11270,11271],{"class":88},"and_hms_opt",[71,11273,475],{"class":445},[71,11275,11276],{"class":88},"8",[71,11278,452],{"class":445},[71,11280,11281],{"class":88}," 30",[71,11283,452],{"class":445},[71,11285,1868],{"class":88},[71,11287,681],{"class":445},[71,11289,626],{"class":99},[71,11291,1496],{"class":88},[71,11293,2019],{"class":445},[71,11295,11296],{"class":73,"line":781},[71,11297,464],{"emptyLinePlaceholder":463},[71,11299,11300,11302],{"class":73,"line":796},[71,11301,595],{"class":77},[71,11303,11304],{"class":81}," 4) 时间运算\n",[71,11306,11307,11309,11312,11314,11316,11318,11320,11322,11324,11326,11328,11331,11333,11335],{"class":73,"line":828},[71,11308,604],{"class":99},[71,11310,11311],{"class":507}," tomorrow",[71,11313,610],{"class":99},[71,11315,10911],{"class":439},[71,11317,442],{"class":99},[71,11319,8526],{"class":88},[71,11321,574],{"class":445},[71,11323,7362],{"class":99},[71,11325,2972],{"class":439},[71,11327,442],{"class":99},[71,11329,11330],{"class":88},"days",[71,11332,475],{"class":445},[71,11334,2061],{"class":88},[71,11336,765],{"class":445},[71,11338,11339,11341,11344,11346,11348,11350,11352,11354,11356,11358,11360,11363,11365,11367,11369,11371,11373,11375,11378,11380,11383],{"class":73,"line":848},[71,11340,604],{"class":99},[71,11342,11343],{"class":507}," deadline",[71,11345,610],{"class":99},[71,11347,10911],{"class":439},[71,11349,442],{"class":99},[71,11351,8526],{"class":88},[71,11353,574],{"class":445},[71,11355,7362],{"class":99},[71,11357,2972],{"class":439},[71,11359,442],{"class":99},[71,11361,11362],{"class":88},"hours",[71,11364,475],{"class":445},[71,11366,4432],{"class":88},[71,11368,681],{"class":445},[71,11370,7362],{"class":99},[71,11372,2972],{"class":439},[71,11374,442],{"class":99},[71,11376,11377],{"class":88},"minutes",[71,11379,475],{"class":445},[71,11381,11382],{"class":88},"30",[71,11384,765],{"class":445},[71,11386,11387,11389,11392,11394,11396,11399,11401,11403,11405,11407,11409],{"class":73,"line":857},[71,11388,604],{"class":99},[71,11390,11391],{"class":507}," diff",[71,11393,610],{"class":99},[71,11395,11343],{"class":507},[71,11397,11398],{"class":99}," -",[71,11400,10911],{"class":439},[71,11402,442],{"class":99},[71,11404,8526],{"class":88},[71,11406,4305],{"class":445},[71,11408,6468],{"class":77},[71,11410,11411],{"class":81}," 减法得 Duration\n",[71,11413,11414,11416,11418,11421,11423,11426,11428,11430,11432,11435],{"class":73,"line":2022},[71,11415,693],{"class":88},[71,11417,475],{"class":445},[71,11419,11420],{"class":92},"\"还剩 ",[71,11422,741],{"class":445},[71,11424,11425],{"class":92}," 秒\"",[71,11427,452],{"class":445},[71,11429,11391],{"class":507},[71,11431,626],{"class":99},[71,11433,11434],{"class":88},"num_seconds",[71,11436,2019],{"class":445},[71,11438,11439,11441,11443,11445,11447,11449,11451,11453,11455,11457],{"class":73,"line":2027},[71,11440,604],{"class":99},[71,11442,3350],{"class":507},[71,11444,610],{"class":99},[71,11446,3162],{"class":445},[71,11448,11266],{"class":507},[71,11450,452],{"class":445},[71,11452,11250],{"class":507},[71,11454,452],{"class":445},[71,11456,11311],{"class":507},[71,11458,765],{"class":445},[71,11460,11461],{"class":73,"line":2035},[71,11462,464],{"emptyLinePlaceholder":463},[71,11464,11465,11467],{"class":73,"line":2068},[71,11466,595],{"class":77},[71,11468,11469],{"class":81}," 5) 取字段\n",[71,11471,11472,11474,11476,11478,11480,11483,11485,11488,11490,11493,11495,11497,11499,11501,11503,11505,11507,11510,11512,11514,11516,11519,11521,11523,11525,11528,11530,11532,11534,11537],{"class":73,"line":2095},[71,11473,693],{"class":88},[71,11475,475],{"class":445},[71,11477,698],{"class":92},[71,11479,741],{"class":445},[71,11481,11482],{"class":92},"年",[71,11484,741],{"class":445},[71,11486,11487],{"class":92},"月",[71,11489,741],{"class":445},[71,11491,11492],{"class":92},"日 星期",[71,11494,446],{"class":445},[71,11496,2107],{"class":92},[71,11498,706],{"class":445},[71,11500,698],{"class":92},[71,11502,452],{"class":445},[71,11504,10992],{"class":507},[71,11506,626],{"class":99},[71,11508,11509],{"class":88},"year",[71,11511,632],{"class":445},[71,11513,10992],{"class":507},[71,11515,626],{"class":99},[71,11517,11518],{"class":88},"month",[71,11520,632],{"class":445},[71,11522,10992],{"class":507},[71,11524,626],{"class":99},[71,11526,11527],{"class":88},"day",[71,11529,632],{"class":445},[71,11531,10992],{"class":507},[71,11533,626],{"class":99},[71,11535,11536],{"class":88},"weekday",[71,11538,2019],{"class":445},[71,11540,11541],{"class":73,"line":2131},[71,11542,559],{"class":445},[14,11544,11545,11548],{},[18,11546,11547],{},"为什么「存 UTC、展示转本地」是铁律？"," 夏令时、用户跨时区、服务器迁移时区——只要存了本地时间，这些场景全是坑。UTC 是无歧义的单点时间，本地时间只是「某时区下的一种显示格式」。",[57,11550,11552],{"id":11551},"_83-与-serde-配合","8.3 与 serde 配合",[62,11554,11556],{"className":427,"code":11555,"language":429,"meta":67,"style":67},"use chrono::{DateTime, Utc, NaiveDate};\nuse serde::{Serialize, Deserialize};\n\n#[derive(Serialize, Deserialize)]\nstruct Event {\n    name: String,\n    at: DateTime\u003CUtc>,                       \u002F\u002F 默认序列化为 RFC3339 字符串\n    #[serde(with = \"date_fmt\")]              \u002F\u002F 自定义格式时用 with + 模块\n    day: NaiveDate,\n}\n\nmod date_fmt {\n    use chrono::{NaiveDate, serde::ts_microseconds as _}; \u002F\u002F 只是示意，下面手写\n    use serde::{Deserialize, Deserializer, Serializer};\n\n    const FMT: &str = \"%Y\u002F%m\u002F%d\";\n    pub fn serialize\u003CS: Serializer>(d: &NaiveDate, s: S) -> Result\u003CS::Ok, S::Error> {\n        s.serialize_str(&d.format(FMT).to_string())\n    }\n    pub fn deserialize\u003C'de, D: Deserializer\u003C'de>>(d: D) -> Result\u003CNaiveDate, D::Error> {\n        let s = String::deserialize(d)?;\n        NaiveDate::parse_from_str(&s, FMT).map_err(serde::de::Error::custom)\n    }\n}\n",[52,11557,11558,11581,11599,11603,11619,11628,11638,11658,11681,11692,11696,11700,11710,11746,11770,11774,11793,11858,11891,11895,11954,11979,12026,12030],{"__ignoreMap":67},[71,11559,11560,11562,11564,11566,11568,11571,11573,11575,11577,11579],{"class":73,"line":74},[71,11561,436],{"class":99},[71,11563,10899],{"class":439},[71,11565,442],{"class":99},[71,11567,446],{"class":445},[71,11569,11570],{"class":439},"DateTime",[71,11572,452],{"class":445},[71,11574,10911],{"class":439},[71,11576,452],{"class":445},[71,11578,10916],{"class":439},[71,11580,458],{"class":445},[71,11582,11583,11585,11587,11589,11591,11593,11595,11597],{"class":73,"line":85},[71,11584,436],{"class":99},[71,11586,96],{"class":439},[71,11588,442],{"class":99},[71,11590,446],{"class":445},[71,11592,478],{"class":439},[71,11594,452],{"class":445},[71,11596,483],{"class":439},[71,11598,458],{"class":445},[71,11600,11601],{"class":73,"line":106},[71,11602,464],{"emptyLinePlaceholder":463},[71,11604,11605,11607,11609,11611,11613,11615,11617],{"class":73,"line":121},[71,11606,469],{"class":445},[71,11608,176],{"class":472},[71,11610,475],{"class":445},[71,11612,478],{"class":439},[71,11614,452],{"class":445},[71,11616,483],{"class":439},[71,11618,491],{"class":445},[71,11620,11621,11623,11626],{"class":73,"line":259},[71,11622,496],{"class":99},[71,11624,11625],{"class":439}," Event",[71,11627,502],{"class":445},[71,11629,11630,11632,11634,11636],{"class":73,"line":273},[71,11631,508],{"class":507},[71,11633,511],{"class":99},[71,11635,514],{"class":439},[71,11637,517],{"class":445},[71,11639,11640,11643,11645,11647,11649,11651,11653,11655],{"class":73,"line":286},[71,11641,11642],{"class":507},"    at",[71,11644,511],{"class":99},[71,11646,10921],{"class":439},[71,11648,542],{"class":445},[71,11650,11172],{"class":439},[71,11652,548],{"class":445},[71,11654,2357],{"class":77},[71,11656,11657],{"class":81}," 默认序列化为 RFC3339 字符串\n",[71,11659,11660,11662,11664,11666,11669,11671,11674,11676,11678],{"class":73,"line":300},[71,11661,898],{"class":445},[71,11663,901],{"class":472},[71,11665,475],{"class":445},[71,11667,11668],{"class":472},"with ",[71,11670,909],{"class":99},[71,11672,11673],{"class":92}," \"date_fmt\"",[71,11675,915],{"class":445},[71,11677,1927],{"class":77},[71,11679,11680],{"class":81}," 自定义格式时用 with + 模块\n",[71,11682,11683,11686,11688,11690],{"class":73,"line":316},[71,11684,11685],{"class":507},"    day",[71,11687,511],{"class":99},[71,11689,10916],{"class":439},[71,11691,517],{"class":445},[71,11693,11694],{"class":73,"line":330},[71,11695,559],{"class":445},[71,11697,11698],{"class":73,"line":352},[71,11699,464],{"emptyLinePlaceholder":463},[71,11701,11702,11705,11708],{"class":73,"line":601},[71,11703,11704],{"class":99},"mod",[71,11706,11707],{"class":88}," date_fmt",[71,11709,502],{"class":445},[71,11711,11712,11715,11717,11719,11721,11724,11726,11728,11730,11733,11736,11738,11741,11743],{"class":73,"line":656},[71,11713,11714],{"class":99},"    use",[71,11716,10899],{"class":439},[71,11718,442],{"class":99},[71,11720,446],{"class":445},[71,11722,11723],{"class":439},"NaiveDate",[71,11725,452],{"class":445},[71,11727,96],{"class":439},[71,11729,442],{"class":99},[71,11731,11732],{"class":472},"ts_microseconds ",[71,11734,11735],{"class":99},"as",[71,11737,3350],{"class":472},[71,11739,11740],{"class":445},"};",[71,11742,551],{"class":77},[71,11744,11745],{"class":81}," 只是示意，下面手写\n",[71,11747,11748,11750,11752,11754,11756,11758,11760,11763,11765,11768],{"class":73,"line":690},[71,11749,11714],{"class":99},[71,11751,96],{"class":439},[71,11753,442],{"class":99},[71,11755,446],{"class":445},[71,11757,449],{"class":439},[71,11759,452],{"class":445},[71,11761,11762],{"class":439}," Deserializer",[71,11764,452],{"class":445},[71,11766,11767],{"class":439}," Serializer",[71,11769,458],{"class":445},[71,11771,11772],{"class":73,"line":719},[71,11773,464],{"emptyLinePlaceholder":463},[71,11775,11776,11779,11782,11784,11786,11788,11791],{"class":73,"line":724},[71,11777,11778],{"class":99},"    const",[71,11780,11781],{"class":99}," FMT:",[71,11783,1714],{"class":99},[71,11785,1717],{"class":439},[71,11787,610],{"class":99},[71,11789,11790],{"class":92}," \"%Y\u002F%m\u002F%d\"",[71,11792,687],{"class":445},[71,11794,11795,11798,11800,11803,11805,11808,11810,11812,11814,11816,11818,11820,11822,11824,11826,11828,11831,11833,11835,11837,11839,11841,11843,11846,11848,11850,11852,11854,11856],{"class":73,"line":732},[71,11796,11797],{"class":99},"    pub",[71,11799,2793],{"class":99},[71,11801,11802],{"class":88}," serialize",[71,11804,542],{"class":445},[71,11806,11807],{"class":439},"S",[71,11809,511],{"class":99},[71,11811,11767],{"class":439},[71,11813,4389],{"class":445},[71,11815,11266],{"class":507},[71,11817,511],{"class":99},[71,11819,1714],{"class":99},[71,11821,11723],{"class":439},[71,11823,452],{"class":445},[71,11825,661],{"class":507},[71,11827,511],{"class":99},[71,11829,11830],{"class":439}," S",[71,11832,681],{"class":445},[71,11834,577],{"class":99},[71,11836,1661],{"class":439},[71,11838,542],{"class":445},[71,11840,11807],{"class":439},[71,11842,442],{"class":99},[71,11844,11845],{"class":439},"Ok",[71,11847,452],{"class":445},[71,11849,11830],{"class":439},[71,11851,442],{"class":99},[71,11853,2192],{"class":439},[71,11855,1730],{"class":445},[71,11857,502],{"class":445},[71,11859,11860,11863,11865,11868,11870,11872,11874,11876,11878,11880,11883,11885,11887,11889],{"class":73,"line":768},[71,11861,11862],{"class":507},"        s",[71,11864,626],{"class":99},[71,11866,11867],{"class":88},"serialize_str",[71,11869,475],{"class":445},[71,11871,675],{"class":99},[71,11873,11266],{"class":507},[71,11875,626],{"class":99},[71,11877,9810],{"class":88},[71,11879,475],{"class":445},[71,11881,11882],{"class":99},"FMT",[71,11884,681],{"class":445},[71,11886,626],{"class":99},[71,11888,670],{"class":88},[71,11890,2500],{"class":445},[71,11892,11893],{"class":73,"line":773},[71,11894,1935],{"class":445},[71,11896,11897,11899,11901,11904,11907,11910,11912,11915,11917,11919,11921,11923,11926,11928,11930,11932,11934,11936,11938,11940,11942,11944,11946,11948,11950,11952],{"class":73,"line":781},[71,11898,11797],{"class":99},[71,11900,2793],{"class":99},[71,11902,11903],{"class":88}," deserialize",[71,11905,11906],{"class":445},"\u003C'",[71,11908,11909],{"class":439},"de",[71,11911,452],{"class":445},[71,11913,11914],{"class":439}," D",[71,11916,511],{"class":99},[71,11918,11762],{"class":439},[71,11920,11906],{"class":445},[71,11922,11909],{"class":439},[71,11924,11925],{"class":445},">>(",[71,11927,11266],{"class":507},[71,11929,511],{"class":99},[71,11931,11914],{"class":439},[71,11933,681],{"class":445},[71,11935,577],{"class":99},[71,11937,1661],{"class":439},[71,11939,542],{"class":445},[71,11941,11723],{"class":439},[71,11943,452],{"class":445},[71,11945,11914],{"class":439},[71,11947,442],{"class":99},[71,11949,2192],{"class":439},[71,11951,1730],{"class":445},[71,11953,502],{"class":445},[71,11955,11956,11958,11960,11962,11964,11966,11969,11971,11973,11975,11977],{"class":73,"line":796},[71,11957,4187],{"class":99},[71,11959,661],{"class":507},[71,11961,610],{"class":99},[71,11963,514],{"class":439},[71,11965,442],{"class":99},[71,11967,11968],{"class":88},"deserialize",[71,11970,475],{"class":445},[71,11972,11266],{"class":507},[71,11974,681],{"class":445},[71,11976,684],{"class":99},[71,11978,687],{"class":445},[71,11980,11981,11984,11986,11989,11991,11993,11995,11997,12000,12002,12004,12007,12009,12011,12013,12015,12017,12019,12021,12024],{"class":73,"line":828},[71,11982,11983],{"class":439},"        NaiveDate",[71,11985,442],{"class":99},[71,11987,11988],{"class":88},"parse_from_str",[71,11990,475],{"class":445},[71,11992,675],{"class":99},[71,11994,703],{"class":507},[71,11996,452],{"class":445},[71,11998,11999],{"class":99}," FMT",[71,12001,681],{"class":445},[71,12003,626],{"class":99},[71,12005,12006],{"class":88},"map_err",[71,12008,475],{"class":445},[71,12010,901],{"class":439},[71,12012,442],{"class":99},[71,12014,11909],{"class":439},[71,12016,442],{"class":99},[71,12018,2192],{"class":439},[71,12020,442],{"class":99},[71,12022,12023],{"class":507},"custom",[71,12025,1770],{"class":445},[71,12027,12028],{"class":73,"line":848},[71,12029,1935],{"class":445},[71,12031,12032],{"class":73,"line":857},[71,12033,559],{"class":445},[11,12035,12036],{},[14,12037,12038,12039,2687,12042,12045,12046,12049],{},"小知识：chrono 自带 ",[52,12040,12041],{},"chrono::serde::ts_seconds",[52,12043,12044],{},"ts_milliseconds"," 模块，",[52,12047,12048],{},"#[serde(with = \"chrono::serde::ts_seconds\")]"," 可以直接把时间序列化为时间戳数字，无需手写。",[23,12051],{},[26,12053,12055],{"id":12054},"第-9-章-rand随机数","第 9 章 rand：随机数",[62,12057,12059],{"className":143,"code":12058,"language":145,"meta":67,"style":67},"[dependencies]\nrand = \"0.9\"\n",[52,12060,12061,12065],{"__ignoreMap":67},[71,12062,12063],{"class":73,"line":74},[71,12064,152],{},[71,12066,12067],{"class":73,"line":85},[71,12068,12069],{},"rand = \"0.9\"\n",[11,12071,12072],{},[14,12073,12074,12075,12078,12079,2726,12082,12078,12085,2726,12088,12078,12091,375],{},"rand 0.9（2025 年起的主流版本）API 有变化：",[52,12076,12077],{},"thread_rng()"," → ",[52,12080,12081],{},"rng()",[52,12083,12084],{},"gen()",[52,12086,12087],{},"random()",[52,12089,12090],{},"gen_range()",[52,12092,12093],{},"random_range()",[62,12095,12097],{"className":427,"code":12096,"language":429,"meta":67,"style":67},"use rand::prelude::*;\n\nfn main() {\n    let mut rng = rand::rng();          \u002F\u002F 线程局部、密码学安全的默认随机源\n\n    \u002F\u002F 1) 基本类型\n    let x: u32 = rng.random();          \u002F\u002F 任意实现了相应 trait 的类型\n    let b: bool = rng.random();\n    let f: f64 = rng.random();          \u002F\u002F [0, 1) 均匀分布\n\n    \u002F\u002F 2) 范围随机\n    let dice = rng.random_range(1..=6);         \u002F\u002F 1-6 含 6\n    let score = rng.random_range(0.0..100.0);   \u002F\u002F 浮点也行\n\n    \u002F\u002F 3) 概率\n    if rng.random_bool(0.3) { println!(\"30% 概率命中\"); }\n\n    \u002F\u002F 4) 序列操作\n    let mut cards = vec![\"A\", \"K\", \"Q\", \"J\"];\n    cards.shuffle(&mut rng);                       \u002F\u002F 洗牌\n    let pick = cards.choose(&mut rng).unwrap();    \u002F\u002F 随机选一个\n    let picks: Vec\u003C_> = cards.choose_multiple(&mut rng, 2).collect(); \u002F\u002F 不重复选多个\n\n    \u002F\u002F 5) 随机字符串（验证码、临时文件名）\n    let code: String = (0..6).map(|_| rng.random_range(0..10).to_string()).collect();\n    let token: String = rand::distr::Alphanumeric\n        .sample_string(&mut rng, 16);              \u002F\u002F 字母数字混合 16 位\n\n    println!(\"{x} {b} {f} {dice} {score} {pick} {picks:?} {code} {token}\");\n\n    \u002F\u002F 6) 需要可复现（测试、仿真）时，用固定种子的生成器\n    let mut seeded = rand::rngs::StdRng::seed_from_u64(42); \u002F\u002F 同种子必然产生同序列\n    let _v: u64 = seeded.random();\n}\n",[52,12098,12099,12116,12120,12130,12155,12159,12166,12194,12215,12241,12245,12252,12285,12323,12327,12334,12368,12372,12379,12415,12438,12473,12523,12527,12534,12598,12623,12648,12652,12726,12730,12737,12777,12798],{"__ignoreMap":67},[71,12100,12101,12103,12106,12108,12111,12114],{"class":73,"line":74},[71,12102,436],{"class":99},[71,12104,12105],{"class":439}," rand",[71,12107,442],{"class":99},[71,12109,12110],{"class":439},"prelude",[71,12112,12113],{"class":99},"::*",[71,12115,687],{"class":445},[71,12117,12118],{"class":73,"line":85},[71,12119,464],{"emptyLinePlaceholder":463},[71,12121,12122,12124,12126,12128],{"class":73,"line":106},[71,12123,568],{"class":99},[71,12125,571],{"class":88},[71,12127,574],{"class":445},[71,12129,502],{"class":445},[71,12131,12132,12134,12136,12139,12141,12143,12145,12148,12150,12152],{"class":73,"line":121},[71,12133,604],{"class":99},[71,12135,3780],{"class":99},[71,12137,12138],{"class":507}," rng",[71,12140,610],{"class":99},[71,12142,12105],{"class":439},[71,12144,442],{"class":99},[71,12146,12147],{"class":88},"rng",[71,12149,4305],{"class":445},[71,12151,1032],{"class":77},[71,12153,12154],{"class":81}," 线程局部、密码学安全的默认随机源\n",[71,12156,12157],{"class":73,"line":259},[71,12158,464],{"emptyLinePlaceholder":463},[71,12160,12161,12163],{"class":73,"line":273},[71,12162,595],{"class":77},[71,12164,12165],{"class":81}," 1) 基本类型\n",[71,12167,12168,12170,12173,12175,12178,12180,12182,12184,12187,12189,12191],{"class":73,"line":286},[71,12169,604],{"class":99},[71,12171,12172],{"class":507}," x",[71,12174,511],{"class":99},[71,12176,12177],{"class":439}," u32",[71,12179,610],{"class":99},[71,12181,12138],{"class":507},[71,12183,626],{"class":99},[71,12185,12186],{"class":88},"random",[71,12188,4305],{"class":445},[71,12190,1032],{"class":77},[71,12192,12193],{"class":81}," 任意实现了相应 trait 的类型\n",[71,12195,12196,12198,12201,12203,12205,12207,12209,12211,12213],{"class":73,"line":300},[71,12197,604],{"class":99},[71,12199,12200],{"class":507}," b",[71,12202,511],{"class":99},[71,12204,9614],{"class":439},[71,12206,610],{"class":99},[71,12208,12138],{"class":507},[71,12210,626],{"class":99},[71,12212,12186],{"class":88},[71,12214,1499],{"class":445},[71,12216,12217,12219,12221,12223,12226,12228,12230,12232,12234,12236,12238],{"class":73,"line":316},[71,12218,604],{"class":99},[71,12220,3783],{"class":507},[71,12222,511],{"class":99},[71,12224,12225],{"class":439}," f64",[71,12227,610],{"class":99},[71,12229,12138],{"class":507},[71,12231,626],{"class":99},[71,12233,12186],{"class":88},[71,12235,4305],{"class":445},[71,12237,1032],{"class":77},[71,12239,12240],{"class":81}," [0, 1) 均匀分布\n",[71,12242,12243],{"class":73,"line":330},[71,12244,464],{"emptyLinePlaceholder":463},[71,12246,12247,12249],{"class":73,"line":352},[71,12248,595],{"class":77},[71,12250,12251],{"class":81}," 2) 范围随机\n",[71,12253,12254,12256,12259,12261,12263,12265,12268,12270,12272,12274,12277,12279,12282],{"class":73,"line":601},[71,12255,604],{"class":99},[71,12257,12258],{"class":507}," dice",[71,12260,610],{"class":99},[71,12262,12138],{"class":507},[71,12264,626],{"class":99},[71,12266,12267],{"class":88},"random_range",[71,12269,475],{"class":445},[71,12271,2061],{"class":88},[71,12273,9395],{"class":99},[71,12275,12276],{"class":88},"6",[71,12278,711],{"class":445},[71,12280,12281],{"class":77},"         \u002F\u002F",[71,12283,12284],{"class":81}," 1-6 含 6\n",[71,12286,12287,12289,12292,12294,12296,12298,12300,12302,12304,12306,12308,12310,12312,12314,12316,12318,12320],{"class":73,"line":656},[71,12288,604],{"class":99},[71,12290,12291],{"class":507}," score",[71,12293,610],{"class":99},[71,12295,12138],{"class":507},[71,12297,626],{"class":99},[71,12299,12267],{"class":88},[71,12301,475],{"class":445},[71,12303,1329],{"class":88},[71,12305,626],{"class":445},[71,12307,1329],{"class":88},[71,12309,4178],{"class":99},[71,12311,4392],{"class":88},[71,12313,626],{"class":445},[71,12315,1329],{"class":88},[71,12317,711],{"class":445},[71,12319,1886],{"class":77},[71,12321,12322],{"class":81}," 浮点也行\n",[71,12324,12325],{"class":73,"line":690},[71,12326,464],{"emptyLinePlaceholder":463},[71,12328,12329,12331],{"class":73,"line":719},[71,12330,595],{"class":77},[71,12332,12333],{"class":81}," 3) 概率\n",[71,12335,12336,12338,12340,12342,12345,12347,12349,12351,12353,12355,12357,12359,12361,12364,12366],{"class":73,"line":724},[71,12337,1894],{"class":99},[71,12339,12138],{"class":507},[71,12341,626],{"class":99},[71,12343,12344],{"class":88},"random_bool",[71,12346,475],{"class":445},[71,12348,1329],{"class":88},[71,12350,626],{"class":445},[71,12352,4432],{"class":88},[71,12354,681],{"class":445},[71,12356,615],{"class":445},[71,12358,2566],{"class":88},[71,12360,475],{"class":445},[71,12362,12363],{"class":92},"\"30% 概率命中\"",[71,12365,711],{"class":445},[71,12367,1180],{"class":445},[71,12369,12370],{"class":73,"line":732},[71,12371,464],{"emptyLinePlaceholder":463},[71,12373,12374,12376],{"class":73,"line":768},[71,12375,595],{"class":77},[71,12377,12378],{"class":81}," 4) 序列操作\n",[71,12380,12381,12383,12385,12388,12390,12392,12394,12397,12399,12402,12404,12407,12409,12412],{"class":73,"line":773},[71,12382,604],{"class":99},[71,12384,3780],{"class":99},[71,12386,12387],{"class":507}," cards",[71,12389,610],{"class":99},[71,12391,4160],{"class":88},[71,12393,1315],{"class":445},[71,12395,12396],{"class":92},"\"A\"",[71,12398,452],{"class":445},[71,12400,12401],{"class":92}," \"K\"",[71,12403,452],{"class":445},[71,12405,12406],{"class":92}," \"Q\"",[71,12408,452],{"class":445},[71,12410,12411],{"class":92}," \"J\"",[71,12413,12414],{"class":445},"];\n",[71,12416,12417,12420,12422,12425,12427,12429,12431,12433,12435],{"class":73,"line":781},[71,12418,12419],{"class":507},"    cards",[71,12421,626],{"class":99},[71,12423,12424],{"class":88},"shuffle",[71,12426,475],{"class":445},[71,12428,3907],{"class":99},[71,12430,12138],{"class":507},[71,12432,711],{"class":445},[71,12434,2357],{"class":77},[71,12436,12437],{"class":81}," 洗牌\n",[71,12439,12440,12442,12445,12447,12449,12451,12454,12456,12458,12460,12462,12464,12466,12468,12470],{"class":73,"line":796},[71,12441,604],{"class":99},[71,12443,12444],{"class":507}," pick",[71,12446,610],{"class":99},[71,12448,12387],{"class":507},[71,12450,626],{"class":99},[71,12452,12453],{"class":88},"choose",[71,12455,475],{"class":445},[71,12457,3907],{"class":99},[71,12459,12138],{"class":507},[71,12461,681],{"class":445},[71,12463,626],{"class":99},[71,12465,1496],{"class":88},[71,12467,4305],{"class":445},[71,12469,595],{"class":77},[71,12471,12472],{"class":81}," 随机选一个\n",[71,12474,12475,12477,12480,12482,12484,12486,12488,12490,12492,12494,12496,12499,12501,12503,12505,12507,12509,12511,12513,12516,12518,12520],{"class":73,"line":828},[71,12476,604],{"class":99},[71,12478,12479],{"class":507}," picks",[71,12481,511],{"class":99},[71,12483,966],{"class":439},[71,12485,542],{"class":445},[71,12487,3584],{"class":507},[71,12489,1730],{"class":445},[71,12491,610],{"class":99},[71,12493,12387],{"class":507},[71,12495,626],{"class":99},[71,12497,12498],{"class":88},"choose_multiple",[71,12500,475],{"class":445},[71,12502,3907],{"class":99},[71,12504,12138],{"class":507},[71,12506,452],{"class":445},[71,12508,11217],{"class":88},[71,12510,681],{"class":445},[71,12512,626],{"class":99},[71,12514,12515],{"class":88},"collect",[71,12517,4305],{"class":445},[71,12519,551],{"class":77},[71,12521,12522],{"class":81}," 不重复选多个\n",[71,12524,12525],{"class":73,"line":848},[71,12526,464],{"emptyLinePlaceholder":463},[71,12528,12529,12531],{"class":73,"line":857},[71,12530,595],{"class":77},[71,12532,12533],{"class":81}," 5) 随机字符串（验证码、临时文件名）\n",[71,12535,12536,12538,12541,12543,12545,12547,12549,12551,12553,12555,12557,12559,12562,12564,12566,12568,12570,12572,12574,12576,12578,12580,12582,12584,12586,12588,12590,12592,12594,12596],{"class":73,"line":2022},[71,12537,604],{"class":99},[71,12539,12540],{"class":507}," code",[71,12542,511],{"class":99},[71,12544,514],{"class":439},[71,12546,610],{"class":99},[71,12548,3162],{"class":445},[71,12550,1329],{"class":88},[71,12552,4178],{"class":99},[71,12554,12276],{"class":88},[71,12556,681],{"class":445},[71,12558,626],{"class":99},[71,12560,12561],{"class":88},"map",[71,12563,475],{"class":445},[71,12565,7400],{"class":99},[71,12567,3584],{"class":507},[71,12569,7400],{"class":99},[71,12571,12138],{"class":507},[71,12573,626],{"class":99},[71,12575,12267],{"class":88},[71,12577,475],{"class":445},[71,12579,1329],{"class":88},[71,12581,4178],{"class":99},[71,12583,3326],{"class":88},[71,12585,681],{"class":445},[71,12587,626],{"class":99},[71,12589,670],{"class":88},[71,12591,3044],{"class":445},[71,12593,626],{"class":99},[71,12595,12515],{"class":88},[71,12597,1499],{"class":445},[71,12599,12600,12602,12605,12607,12609,12611,12613,12615,12618,12620],{"class":73,"line":2027},[71,12601,604],{"class":99},[71,12603,12604],{"class":507}," token",[71,12606,511],{"class":99},[71,12608,514],{"class":439},[71,12610,610],{"class":99},[71,12612,12105],{"class":439},[71,12614,442],{"class":99},[71,12616,12617],{"class":439},"distr",[71,12619,442],{"class":99},[71,12621,12622],{"class":439},"Alphanumeric\n",[71,12624,12625,12627,12630,12632,12634,12636,12638,12641,12643,12645],{"class":73,"line":2035},[71,12626,1775],{"class":99},[71,12628,12629],{"class":88},"sample_string",[71,12631,475],{"class":445},[71,12633,3907],{"class":99},[71,12635,12138],{"class":507},[71,12637,452],{"class":445},[71,12639,12640],{"class":88}," 16",[71,12642,711],{"class":445},[71,12644,1927],{"class":77},[71,12646,12647],{"class":81}," 字母数字混合 16 位\n",[71,12649,12650],{"class":73,"line":2068},[71,12651,464],{"emptyLinePlaceholder":463},[71,12653,12654,12656,12658,12660,12662,12665,12667,12669,12672,12674,12676,12679,12681,12683,12686,12688,12690,12693,12695,12697,12700,12702,12704,12707,12709,12711,12713,12715,12717,12720,12722,12724],{"class":73,"line":2095},[71,12655,693],{"class":88},[71,12657,475],{"class":445},[71,12659,698],{"class":92},[71,12661,446],{"class":445},[71,12663,12664],{"class":92},"x",[71,12666,706],{"class":445},[71,12668,615],{"class":445},[71,12670,12671],{"class":92},"b",[71,12673,706],{"class":445},[71,12675,615],{"class":445},[71,12677,12678],{"class":92},"f",[71,12680,706],{"class":445},[71,12682,615],{"class":445},[71,12684,12685],{"class":92},"dice",[71,12687,706],{"class":445},[71,12689,615],{"class":445},[71,12691,12692],{"class":92},"score",[71,12694,706],{"class":445},[71,12696,615],{"class":445},[71,12698,12699],{"class":92},"pick",[71,12701,706],{"class":445},[71,12703,615],{"class":445},[71,12705,12706],{"class":92},"picks:?",[71,12708,706],{"class":445},[71,12710,615],{"class":445},[71,12712,52],{"class":92},[71,12714,706],{"class":445},[71,12716,615],{"class":445},[71,12718,12719],{"class":92},"token",[71,12721,706],{"class":445},[71,12723,698],{"class":92},[71,12725,765],{"class":445},[71,12727,12728],{"class":73,"line":2131},[71,12729,464],{"emptyLinePlaceholder":463},[71,12731,12732,12734],{"class":73,"line":2136},[71,12733,595],{"class":77},[71,12735,12736],{"class":81}," 6) 需要可复现（测试、仿真）时，用固定种子的生成器\n",[71,12738,12739,12741,12743,12746,12748,12750,12752,12755,12757,12760,12762,12765,12767,12770,12772,12774],{"class":73,"line":2143},[71,12740,604],{"class":99},[71,12742,3780],{"class":99},[71,12744,12745],{"class":507}," seeded",[71,12747,610],{"class":99},[71,12749,12105],{"class":439},[71,12751,442],{"class":99},[71,12753,12754],{"class":439},"rngs",[71,12756,442],{"class":99},[71,12758,12759],{"class":439},"StdRng",[71,12761,442],{"class":99},[71,12763,12764],{"class":88},"seed_from_u64",[71,12766,475],{"class":445},[71,12768,12769],{"class":88},"42",[71,12771,711],{"class":445},[71,12773,551],{"class":77},[71,12775,12776],{"class":81}," 同种子必然产生同序列\n",[71,12778,12779,12781,12784,12786,12788,12790,12792,12794,12796],{"class":73,"line":4626},[71,12780,604],{"class":99},[71,12782,12783],{"class":507}," _v",[71,12785,511],{"class":99},[71,12787,931],{"class":439},[71,12789,610],{"class":99},[71,12791,12745],{"class":507},[71,12793,626],{"class":99},[71,12795,12186],{"class":88},[71,12797,1499],{"class":445},[71,12799,12800],{"class":73,"line":4649},[71,12801,559],{"class":445},[14,12803,12804,12810,12811,12813,12814,12817,12818,12821],{},[18,12805,12806,12807,12809],{},"为什么默认用 ",[52,12808,12081],{}," 而不是自己播种？"," ",[52,12812,12081],{}," 背后是操作系统熵源，不可预测，适合 token、验证码等安全敏感场景。反过来，测试和仿真实验需要",[18,12815,12816],{},"可复现","，这时必须显式用 ",[52,12819,12820],{},"StdRng::seed_from_u64(固定值)","——两种需求对应两种用法，库把选择权交给你，这正是「默认安全、显式可控」的设计。",[23,12823],{},[26,12825,12827],{"id":12826},"第-10-章-regex正则表达式","第 10 章 regex：正则表达式",[62,12829,12831],{"className":143,"code":12830,"language":145,"meta":67,"style":67},"[dependencies]\nregex = \"1\"\n",[52,12832,12833,12837],{"__ignoreMap":67},[71,12834,12835],{"class":73,"line":74},[71,12836,152],{},[71,12838,12839],{"class":73,"line":85},[71,12840,12841],{},"regex = \"1\"\n",[57,12843,12845],{"id":12844},"_101-基本用法","10.1 基本用法",[62,12847,12849],{"className":427,"code":12848,"language":429,"meta":67,"style":67},"use regex::Regex;\n\nfn main() {\n    \u002F\u002F 1) 匹配判断\n    let re = Regex::new(r\"^\\d{4}-\\d{2}-\\d{2}$\").unwrap(); \u002F\u002F r\"...\" 原始字符串，不用转义反斜杠\n    assert!(re.is_match(\"2026-09-28\"));\n    assert!(!re.is_match(\"2026\u002F09\u002F28\"));\n\n    \u002F\u002F 2) 查找\n    let re = Regex::new(r\"\\d+\").unwrap();\n    let m = re.find(\"房间号是 1024 室\").unwrap();\n    println!(\"找到 '{}' 位于 {:?}\", m.as_str(), m.range());\n\n    \u002F\u002F 3) 捕获组：提取结构化数据\n    let re = Regex::new(r\"(\\w+)@(\\w+\\.\\w+)\").unwrap();\n    let text = \"联系我: alice@example.com\";\n    if let Some(caps) = re.captures(text) {\n        println!(\"整体: {}\", &caps[0]);   \u002F\u002F caps[0] 永远是整个匹配\n        println!(\"用户名: {}\", &caps[1]);\n        println!(\"域名: {}\", &caps[2]);\n    }\n\n    \u002F\u002F 命名捕获组：长正则里比数字序号更可读\n    let re = Regex::new(r\"(?\u003Cyear>\\d{4})-(?\u003Cmonth>\\d{2})-(?\u003Cday>\\d{2})\").unwrap();\n    let caps = re.captures(\"2026-09-28\").unwrap();\n    println!(\"{}月{}日\", &caps[\"month\"], &caps[\"day\"]);\n\n    \u002F\u002F 4) 迭代所有匹配\n    for m in re.find_iter(\"2026-09-28 和 2025-01-01\") {\n        println!(\"{:?}\", m.as_str());\n    }\n\n    \u002F\u002F 5) 替换\n    let re = Regex::new(r\"\\d+\").unwrap();\n    let masked = re.replace_all(\"电话 13812345678 备用 01012345\", \"***\");\n    println!(\"{masked}\");\n    \u002F\u002F 用捕获组做模板替换：$name 引用命名组\n    let re = Regex::new(r\"(?\u003Cy>\\d{4})-(?\u003Cm>\\d{2})-(?\u003Cd>\\d{2})\").unwrap();\n    println!(\"{}\", re.replace(\"2026-09-28\", \"$d\u002F$m\u002F$y\")); \u002F\u002F 28\u002F09\u002F2026\n\n    \u002F\u002F 6) 分割\n    let re = Regex::new(r\"[,，;；\\s]+\").unwrap();\n    let parts: Vec\u003C&str> = re.split(\"苹果, 香蕉，橘子；西瓜 桃子\").collect();\n    println!(\"{parts:?}\");\n}\n",[52,12850,12851,12865,12869,12879,12886,12920,12942,12963,12967,12974,13001,13029,13069,13073,13080,13107,13121,13153,13184,13210,13236,13240,13244,13251,13278,13305,13347,13351,13358,13382,13408,13412,13416,13423,13449,13477,13496,13503,13530,13567,13571,13578,13605,13646,13665],{"__ignoreMap":67},[71,12852,12853,12855,12858,12860,12863],{"class":73,"line":74},[71,12854,436],{"class":99},[71,12856,12857],{"class":439}," regex",[71,12859,442],{"class":99},[71,12861,12862],{"class":439},"Regex",[71,12864,687],{"class":445},[71,12866,12867],{"class":73,"line":85},[71,12868,464],{"emptyLinePlaceholder":463},[71,12870,12871,12873,12875,12877],{"class":73,"line":106},[71,12872,568],{"class":99},[71,12874,571],{"class":88},[71,12876,574],{"class":445},[71,12878,502],{"class":445},[71,12880,12881,12883],{"class":73,"line":121},[71,12882,595],{"class":77},[71,12884,12885],{"class":81}," 1) 匹配判断\n",[71,12887,12888,12890,12893,12895,12898,12900,12902,12904,12907,12909,12911,12913,12915,12917],{"class":73,"line":259},[71,12889,604],{"class":99},[71,12891,12892],{"class":507}," re",[71,12894,610],{"class":99},[71,12896,12897],{"class":439}," Regex",[71,12899,442],{"class":99},[71,12901,3876],{"class":88},[71,12903,475],{"class":445},[71,12905,12906],{"class":92},"r\"^\\d{4}-\\d{2}-\\d{2}$\"",[71,12908,681],{"class":445},[71,12910,626],{"class":99},[71,12912,1496],{"class":88},[71,12914,4305],{"class":445},[71,12916,551],{"class":77},[71,12918,12919],{"class":81}," r\"...\" 原始字符串，不用转义反斜杠\n",[71,12921,12922,12925,12927,12930,12932,12935,12937,12940],{"class":73,"line":273},[71,12923,12924],{"class":88},"    assert!",[71,12926,475],{"class":445},[71,12928,12929],{"class":507},"re",[71,12931,626],{"class":99},[71,12933,12934],{"class":88},"is_match",[71,12936,475],{"class":445},[71,12938,12939],{"class":92},"\"2026-09-28\"",[71,12941,4146],{"class":445},[71,12943,12944,12946,12948,12950,12952,12954,12956,12958,12961],{"class":73,"line":286},[71,12945,12924],{"class":88},[71,12947,475],{"class":445},[71,12949,9377],{"class":99},[71,12951,12929],{"class":507},[71,12953,626],{"class":99},[71,12955,12934],{"class":88},[71,12957,475],{"class":445},[71,12959,12960],{"class":92},"\"2026\u002F09\u002F28\"",[71,12962,4146],{"class":445},[71,12964,12965],{"class":73,"line":300},[71,12966,464],{"emptyLinePlaceholder":463},[71,12968,12969,12971],{"class":73,"line":316},[71,12970,595],{"class":77},[71,12972,12973],{"class":81}," 2) 查找\n",[71,12975,12976,12978,12980,12982,12984,12986,12988,12990,12993,12995,12997,12999],{"class":73,"line":330},[71,12977,604],{"class":99},[71,12979,12892],{"class":507},[71,12981,610],{"class":99},[71,12983,12897],{"class":439},[71,12985,442],{"class":99},[71,12987,3876],{"class":88},[71,12989,475],{"class":445},[71,12991,12992],{"class":92},"r\"\\d+\"",[71,12994,681],{"class":445},[71,12996,626],{"class":99},[71,12998,1496],{"class":88},[71,13000,1499],{"class":445},[71,13002,13003,13005,13007,13009,13011,13013,13016,13018,13021,13023,13025,13027],{"class":73,"line":352},[71,13004,604],{"class":99},[71,13006,8171],{"class":507},[71,13008,610],{"class":99},[71,13010,12892],{"class":507},[71,13012,626],{"class":99},[71,13014,13015],{"class":88},"find",[71,13017,475],{"class":445},[71,13019,13020],{"class":92},"\"房间号是 1024 室\"",[71,13022,681],{"class":445},[71,13024,626],{"class":99},[71,13026,1496],{"class":88},[71,13028,1499],{"class":445},[71,13030,13031,13033,13035,13038,13040,13043,13045,13047,13049,13051,13053,13055,13057,13059,13061,13063,13065,13067],{"class":73,"line":601},[71,13032,693],{"class":88},[71,13034,475],{"class":445},[71,13036,13037],{"class":92},"\"找到 '",[71,13039,741],{"class":445},[71,13041,13042],{"class":92},"' 位于 ",[71,13044,446],{"class":445},[71,13046,2107],{"class":92},[71,13048,706],{"class":445},[71,13050,698],{"class":92},[71,13052,452],{"class":445},[71,13054,8171],{"class":507},[71,13056,626],{"class":99},[71,13058,1342],{"class":88},[71,13060,632],{"class":445},[71,13062,8171],{"class":507},[71,13064,626],{"class":99},[71,13066,9388],{"class":88},[71,13068,2019],{"class":445},[71,13070,13071],{"class":73,"line":656},[71,13072,464],{"emptyLinePlaceholder":463},[71,13074,13075,13077],{"class":73,"line":690},[71,13076,595],{"class":77},[71,13078,13079],{"class":81}," 3) 捕获组：提取结构化数据\n",[71,13081,13082,13084,13086,13088,13090,13092,13094,13096,13099,13101,13103,13105],{"class":73,"line":719},[71,13083,604],{"class":99},[71,13085,12892],{"class":507},[71,13087,610],{"class":99},[71,13089,12897],{"class":439},[71,13091,442],{"class":99},[71,13093,3876],{"class":88},[71,13095,475],{"class":445},[71,13097,13098],{"class":92},"r\"(\\w+)@(\\w+\\.\\w+)\"",[71,13100,681],{"class":445},[71,13102,626],{"class":99},[71,13104,1496],{"class":88},[71,13106,1499],{"class":445},[71,13108,13109,13111,13114,13116,13119],{"class":73,"line":724},[71,13110,604],{"class":99},[71,13112,13113],{"class":507}," text",[71,13115,610],{"class":99},[71,13117,13118],{"class":92}," \"联系我: alice@example.com\"",[71,13120,687],{"class":445},[71,13122,13123,13125,13127,13129,13131,13134,13136,13138,13140,13142,13145,13147,13149,13151],{"class":73,"line":732},[71,13124,1894],{"class":99},[71,13126,1298],{"class":99},[71,13128,1301],{"class":439},[71,13130,475],{"class":445},[71,13132,13133],{"class":507},"caps",[71,13135,681],{"class":445},[71,13137,610],{"class":99},[71,13139,12892],{"class":507},[71,13141,626],{"class":99},[71,13143,13144],{"class":88},"captures",[71,13146,475],{"class":445},[71,13148,5030],{"class":507},[71,13150,681],{"class":445},[71,13152,502],{"class":445},[71,13154,13155,13157,13159,13162,13164,13166,13168,13170,13172,13174,13176,13179,13181],{"class":73,"line":768},[71,13156,4521],{"class":88},[71,13158,475],{"class":445},[71,13160,13161],{"class":92},"\"整体: ",[71,13163,741],{"class":445},[71,13165,698],{"class":92},[71,13167,452],{"class":445},[71,13169,1714],{"class":99},[71,13171,13133],{"class":507},[71,13173,1315],{"class":445},[71,13175,1329],{"class":88},[71,13177,13178],{"class":445},"]);",[71,13180,1886],{"class":77},[71,13182,13183],{"class":81}," caps[0] 永远是整个匹配\n",[71,13185,13186,13188,13190,13193,13195,13197,13199,13201,13203,13205,13207],{"class":73,"line":773},[71,13187,4521],{"class":88},[71,13189,475],{"class":445},[71,13191,13192],{"class":92},"\"用户名: ",[71,13194,741],{"class":445},[71,13196,698],{"class":92},[71,13198,452],{"class":445},[71,13200,1714],{"class":99},[71,13202,13133],{"class":507},[71,13204,1315],{"class":445},[71,13206,2061],{"class":88},[71,13208,13209],{"class":445},"]);\n",[71,13211,13212,13214,13216,13219,13221,13223,13225,13227,13229,13231,13234],{"class":73,"line":781},[71,13213,4521],{"class":88},[71,13215,475],{"class":445},[71,13217,13218],{"class":92},"\"域名: ",[71,13220,741],{"class":445},[71,13222,698],{"class":92},[71,13224,452],{"class":445},[71,13226,1714],{"class":99},[71,13228,13133],{"class":507},[71,13230,1315],{"class":445},[71,13232,13233],{"class":88},"2",[71,13235,13209],{"class":445},[71,13237,13238],{"class":73,"line":796},[71,13239,1935],{"class":445},[71,13241,13242],{"class":73,"line":828},[71,13243,464],{"emptyLinePlaceholder":463},[71,13245,13246,13248],{"class":73,"line":848},[71,13247,595],{"class":77},[71,13249,13250],{"class":81}," 命名捕获组：长正则里比数字序号更可读\n",[71,13252,13253,13255,13257,13259,13261,13263,13265,13267,13270,13272,13274,13276],{"class":73,"line":857},[71,13254,604],{"class":99},[71,13256,12892],{"class":507},[71,13258,610],{"class":99},[71,13260,12897],{"class":439},[71,13262,442],{"class":99},[71,13264,3876],{"class":88},[71,13266,475],{"class":445},[71,13268,13269],{"class":92},"r\"(?\u003Cyear>\\d{4})-(?\u003Cmonth>\\d{2})-(?\u003Cday>\\d{2})\"",[71,13271,681],{"class":445},[71,13273,626],{"class":99},[71,13275,1496],{"class":88},[71,13277,1499],{"class":445},[71,13279,13280,13282,13285,13287,13289,13291,13293,13295,13297,13299,13301,13303],{"class":73,"line":2022},[71,13281,604],{"class":99},[71,13283,13284],{"class":507}," caps",[71,13286,610],{"class":99},[71,13288,12892],{"class":507},[71,13290,626],{"class":99},[71,13292,13144],{"class":88},[71,13294,475],{"class":445},[71,13296,12939],{"class":92},[71,13298,681],{"class":445},[71,13300,626],{"class":99},[71,13302,1496],{"class":88},[71,13304,1499],{"class":445},[71,13306,13307,13309,13311,13313,13315,13317,13319,13322,13324,13326,13328,13330,13333,13336,13338,13340,13342,13345],{"class":73,"line":2027},[71,13308,693],{"class":88},[71,13310,475],{"class":445},[71,13312,698],{"class":92},[71,13314,741],{"class":445},[71,13316,11487],{"class":92},[71,13318,741],{"class":445},[71,13320,13321],{"class":92},"日\"",[71,13323,452],{"class":445},[71,13325,1714],{"class":99},[71,13327,13133],{"class":507},[71,13329,1315],{"class":445},[71,13331,13332],{"class":92},"\"month\"",[71,13334,13335],{"class":445},"],",[71,13337,1714],{"class":99},[71,13339,13133],{"class":507},[71,13341,1315],{"class":445},[71,13343,13344],{"class":92},"\"day\"",[71,13346,13209],{"class":445},[71,13348,13349],{"class":73,"line":2035},[71,13350,464],{"emptyLinePlaceholder":463},[71,13352,13353,13355],{"class":73,"line":2068},[71,13354,595],{"class":77},[71,13356,13357],{"class":81}," 4) 迭代所有匹配\n",[71,13359,13360,13362,13364,13366,13368,13370,13373,13375,13378,13380],{"class":73,"line":2095},[71,13361,4168],{"class":99},[71,13363,8171],{"class":507},[71,13365,4173],{"class":99},[71,13367,12892],{"class":507},[71,13369,626],{"class":99},[71,13371,13372],{"class":88},"find_iter",[71,13374,475],{"class":445},[71,13376,13377],{"class":92},"\"2026-09-28 和 2025-01-01\"",[71,13379,681],{"class":445},[71,13381,502],{"class":445},[71,13383,13384,13386,13388,13390,13392,13394,13396,13398,13400,13402,13404,13406],{"class":73,"line":2131},[71,13385,4521],{"class":88},[71,13387,475],{"class":445},[71,13389,698],{"class":92},[71,13391,446],{"class":445},[71,13393,2107],{"class":92},[71,13395,706],{"class":445},[71,13397,698],{"class":92},[71,13399,452],{"class":445},[71,13401,8171],{"class":507},[71,13403,626],{"class":99},[71,13405,1342],{"class":88},[71,13407,2019],{"class":445},[71,13409,13410],{"class":73,"line":2136},[71,13411,1935],{"class":445},[71,13413,13414],{"class":73,"line":2143},[71,13415,464],{"emptyLinePlaceholder":463},[71,13417,13418,13420],{"class":73,"line":4626},[71,13419,595],{"class":77},[71,13421,13422],{"class":81}," 5) 替换\n",[71,13424,13425,13427,13429,13431,13433,13435,13437,13439,13441,13443,13445,13447],{"class":73,"line":4649},[71,13426,604],{"class":99},[71,13428,12892],{"class":507},[71,13430,610],{"class":99},[71,13432,12897],{"class":439},[71,13434,442],{"class":99},[71,13436,3876],{"class":88},[71,13438,475],{"class":445},[71,13440,12992],{"class":92},[71,13442,681],{"class":445},[71,13444,626],{"class":99},[71,13446,1496],{"class":88},[71,13448,1499],{"class":445},[71,13450,13451,13453,13456,13458,13460,13462,13465,13467,13470,13472,13475],{"class":73,"line":4654},[71,13452,604],{"class":99},[71,13454,13455],{"class":507}," masked",[71,13457,610],{"class":99},[71,13459,12892],{"class":507},[71,13461,626],{"class":99},[71,13463,13464],{"class":88},"replace_all",[71,13466,475],{"class":445},[71,13468,13469],{"class":92},"\"电话 13812345678 备用 01012345\"",[71,13471,452],{"class":445},[71,13473,13474],{"class":92}," \"***\"",[71,13476,765],{"class":445},[71,13478,13479,13481,13483,13485,13487,13490,13492,13494],{"class":73,"line":4662},[71,13480,693],{"class":88},[71,13482,475],{"class":445},[71,13484,698],{"class":92},[71,13486,446],{"class":445},[71,13488,13489],{"class":92},"masked",[71,13491,706],{"class":445},[71,13493,698],{"class":92},[71,13495,765],{"class":445},[71,13497,13498,13500],{"class":73,"line":4698},[71,13499,595],{"class":77},[71,13501,13502],{"class":81}," 用捕获组做模板替换：$name 引用命名组\n",[71,13504,13505,13507,13509,13511,13513,13515,13517,13519,13522,13524,13526,13528],{"class":73,"line":4723},[71,13506,604],{"class":99},[71,13508,12892],{"class":507},[71,13510,610],{"class":99},[71,13512,12897],{"class":439},[71,13514,442],{"class":99},[71,13516,3876],{"class":88},[71,13518,475],{"class":445},[71,13520,13521],{"class":92},"r\"(?\u003Cy>\\d{4})-(?\u003Cm>\\d{2})-(?\u003Cd>\\d{2})\"",[71,13523,681],{"class":445},[71,13525,626],{"class":99},[71,13527,1496],{"class":88},[71,13529,1499],{"class":445},[71,13531,13532,13534,13536,13538,13540,13542,13544,13546,13548,13551,13553,13555,13557,13560,13562,13564],{"class":73,"line":4741},[71,13533,693],{"class":88},[71,13535,475],{"class":445},[71,13537,698],{"class":92},[71,13539,741],{"class":445},[71,13541,698],{"class":92},[71,13543,452],{"class":445},[71,13545,12892],{"class":507},[71,13547,626],{"class":99},[71,13549,13550],{"class":88},"replace",[71,13552,475],{"class":445},[71,13554,12939],{"class":92},[71,13556,452],{"class":445},[71,13558,13559],{"class":92}," \"$d\u002F$m\u002F$y\"",[71,13561,2482],{"class":445},[71,13563,551],{"class":77},[71,13565,13566],{"class":81}," 28\u002F09\u002F2026\n",[71,13568,13569],{"class":73,"line":4763},[71,13570,464],{"emptyLinePlaceholder":463},[71,13572,13573,13575],{"class":73,"line":4787},[71,13574,595],{"class":77},[71,13576,13577],{"class":81}," 6) 分割\n",[71,13579,13580,13582,13584,13586,13588,13590,13592,13594,13597,13599,13601,13603],{"class":73,"line":7646},[71,13581,604],{"class":99},[71,13583,12892],{"class":507},[71,13585,610],{"class":99},[71,13587,12897],{"class":439},[71,13589,442],{"class":99},[71,13591,3876],{"class":88},[71,13593,475],{"class":445},[71,13595,13596],{"class":92},"r\"[,，;；\\s]+\"",[71,13598,681],{"class":445},[71,13600,626],{"class":99},[71,13602,1496],{"class":88},[71,13604,1499],{"class":445},[71,13606,13607,13609,13612,13614,13616,13618,13620,13622,13624,13626,13628,13630,13633,13635,13638,13640,13642,13644],{"class":73,"line":7659},[71,13608,604],{"class":99},[71,13610,13611],{"class":507}," parts",[71,13613,511],{"class":99},[71,13615,966],{"class":439},[71,13617,542],{"class":445},[71,13619,675],{"class":99},[71,13621,1717],{"class":439},[71,13623,1730],{"class":445},[71,13625,610],{"class":99},[71,13627,12892],{"class":507},[71,13629,626],{"class":99},[71,13631,13632],{"class":88},"split",[71,13634,475],{"class":445},[71,13636,13637],{"class":92},"\"苹果, 香蕉，橘子；西瓜 桃子\"",[71,13639,681],{"class":445},[71,13641,626],{"class":99},[71,13643,12515],{"class":88},[71,13645,1499],{"class":445},[71,13647,13648,13650,13652,13654,13656,13659,13661,13663],{"class":73,"line":7672},[71,13649,693],{"class":88},[71,13651,475],{"class":445},[71,13653,698],{"class":92},[71,13655,446],{"class":445},[71,13657,13658],{"class":92},"parts:?",[71,13660,706],{"class":445},[71,13662,698],{"class":92},[71,13664,765],{"class":445},[71,13666,13667],{"class":73,"line":7701},[71,13668,559],{"class":445},[57,13670,13672],{"id":13671},"_102-关键性能点正则只编译一次","10.2 关键性能点：正则只编译一次",[62,13674,13676],{"className":427,"code":13675,"language":429,"meta":67,"style":67},"use regex::Regex;\nuse std::sync::LazyLock;\n\n\u002F\u002F Regex::new 要做语法解析+编译自动机，成本不低。\n\u002F\u002F 如果在循环或 handler 里每次 new，是新手最常见的性能坑。\n\u002F\u002F 为什么用 LazyLock？标准库 1.80+ 内置，首次访问时才初始化，线程安全，\n\u002F\u002F 替代过去的 lazy_static \u002F once_cell 第三方宏。\nstatic EMAIL_RE: LazyLock\u003CRegex> = LazyLock::new(|| {\n    Regex::new(r\"^[\\w.+-]+@[\\w-]+\\.[\\w.]+$\").unwrap()\n});\n\nfn validate(email: &str) -> bool {\n    EMAIL_RE.is_match(email) \u002F\u002F 复用同一份编译结果\n}\n",[52,13677,13678,13690,13707,13711,13718,13725,13732,13739,13769,13791,13795,13799,13825,13843],{"__ignoreMap":67},[71,13679,13680,13682,13684,13686,13688],{"class":73,"line":74},[71,13681,436],{"class":99},[71,13683,12857],{"class":439},[71,13685,442],{"class":99},[71,13687,12862],{"class":439},[71,13689,687],{"class":445},[71,13691,13692,13694,13696,13698,13700,13702,13705],{"class":73,"line":85},[71,13693,436],{"class":99},[71,13695,1120],{"class":439},[71,13697,442],{"class":99},[71,13699,4027],{"class":439},[71,13701,442],{"class":99},[71,13703,13704],{"class":439},"LazyLock",[71,13706,687],{"class":445},[71,13708,13709],{"class":73,"line":106},[71,13710,464],{"emptyLinePlaceholder":463},[71,13712,13713,13715],{"class":73,"line":121},[71,13714,998],{"class":77},[71,13716,13717],{"class":81}," Regex::new 要做语法解析+编译自动机，成本不低。\n",[71,13719,13720,13722],{"class":73,"line":259},[71,13721,998],{"class":77},[71,13723,13724],{"class":81}," 如果在循环或 handler 里每次 new，是新手最常见的性能坑。\n",[71,13726,13727,13729],{"class":73,"line":273},[71,13728,998],{"class":77},[71,13730,13731],{"class":81}," 为什么用 LazyLock？标准库 1.80+ 内置，首次访问时才初始化，线程安全，\n",[71,13733,13734,13736],{"class":73,"line":286},[71,13735,998],{"class":77},[71,13737,13738],{"class":81}," 替代过去的 lazy_static \u002F once_cell 第三方宏。\n",[71,13740,13741,13743,13746,13749,13751,13753,13755,13757,13759,13761,13763,13765,13767],{"class":73,"line":300},[71,13742,3443],{"class":99},[71,13744,13745],{"class":99}," EMAIL_RE:",[71,13747,13748],{"class":439}," LazyLock",[71,13750,542],{"class":445},[71,13752,12862],{"class":439},[71,13754,1730],{"class":445},[71,13756,610],{"class":99},[71,13758,13748],{"class":439},[71,13760,442],{"class":99},[71,13762,3876],{"class":88},[71,13764,475],{"class":445},[71,13766,1783],{"class":99},[71,13768,502],{"class":445},[71,13770,13771,13774,13776,13778,13780,13783,13785,13787,13789],{"class":73,"line":316},[71,13772,13773],{"class":439},"    Regex",[71,13775,442],{"class":99},[71,13777,3876],{"class":88},[71,13779,475],{"class":445},[71,13781,13782],{"class":92},"r\"^[\\w.+-]+@[\\w-]+\\.[\\w.]+$\"",[71,13784,681],{"class":445},[71,13786,626],{"class":99},[71,13788,1496],{"class":88},[71,13790,3983],{"class":445},[71,13792,13793],{"class":73,"line":330},[71,13794,1440],{"class":445},[71,13796,13797],{"class":73,"line":352},[71,13798,464],{"emptyLinePlaceholder":463},[71,13800,13801,13803,13806,13808,13811,13813,13815,13817,13819,13821,13823],{"class":73,"line":601},[71,13802,568],{"class":99},[71,13804,13805],{"class":88}," validate",[71,13807,475],{"class":445},[71,13809,13810],{"class":507},"email",[71,13812,511],{"class":99},[71,13814,1714],{"class":99},[71,13816,1717],{"class":439},[71,13818,681],{"class":445},[71,13820,577],{"class":99},[71,13822,9614],{"class":439},[71,13824,502],{"class":445},[71,13826,13827,13830,13832,13834,13836,13838,13840],{"class":73,"line":656},[71,13828,13829],{"class":99},"    EMAIL_RE.",[71,13831,12934],{"class":88},[71,13833,475],{"class":445},[71,13835,13810],{"class":507},[71,13837,681],{"class":445},[71,13839,551],{"class":77},[71,13841,13842],{"class":81}," 复用同一份编译结果\n",[71,13844,13845],{"class":73,"line":690},[71,13846,559],{"class":445},[14,13848,13849,13852,13853,13856,13857,13860],{},[18,13850,13851],{},"为什么 regex 是线性时间保证？"," Rust 的 regex 用的是有限自动机而不是回溯引擎，",[18,13854,13855],{},"不存在","某些语言正则的「灾难性回溯」（恶意输入让匹配耗时指数爆炸）。代价是不支持反向引用、环视（lookahead）——需要这些时用 ",[52,13858,13859],{},"fancy-regex"," crate，并接受性能风险。",[23,13862],{},[26,13864,13866],{"id":13865},"第-11-章-itertools迭代器增强","第 11 章 itertools：迭代器增强",[14,13868,13869],{},"标准库的迭代器适配器（map\u002Ffilter\u002Ffold…）很强大，但 itertools 补齐了常用缺失操作。",[62,13871,13873],{"className":143,"code":13872,"language":145,"meta":67,"style":67},"[dependencies]\nitertools = \"0.14\"\n",[52,13874,13875,13879],{"__ignoreMap":67},[71,13876,13877],{"class":73,"line":74},[71,13878,152],{},[71,13880,13881],{"class":73,"line":85},[71,13882,13883],{},"itertools = \"0.14\"\n",[62,13885,13887],{"className":427,"code":13886,"language":429,"meta":67,"style":67},"use itertools::Itertools;\n\nfn main() {\n    let v = vec![3, 1, 4, 1, 5, 9, 2, 6];\n\n    \u002F\u002F 1) unique：去重（保留顺序）\n    let uniq: Vec\u003C_> = v.iter().unique().collect(); \u002F\u002F [3, 1, 4, 5, 9, 2, 6]\n\n    \u002F\u002F 2) sorted \u002F sorted_by：迭代器直接排序\n    let sorted: Vec\u003C_> = v.iter().sorted().collect();\n    let desc: Vec\u003C_> = v.iter().sorted_by(|a, b| b.cmp(a)).collect();\n\n    \u002F\u002F 3) chunk \u002F chunks：分组\n    let chunks: Vec\u003CVec\u003C_>> = v.iter().chunks(3).into_iter().map(|c| c.collect()).collect();\n    \u002F\u002F [[3,1,4], [1,5,9], [2,6]]\n\n    \u002F\u002F 4) group_by：按 key 分组（类似 SQL GROUP BY）\n    let words = vec![\"apple\", \"avocado\", \"banana\", \"blueberry\", \"cherry\"];\n    let groups = words.iter().into_group_map_by(|w| w.chars().next().unwrap());\n    \u002F\u002F {'a': [apple, avocado], 'b': [banana, blueberry], 'c': [cherry]}\n\n    \u002F\u002F 5) join：迭代器直接拼字符串（比 collect 成 Vec 再 join 少一步）\n    let s = words.iter().join(\", \"); \u002F\u002F \"apple, avocado, ...\"\n\n    \u002F\u002F 6) tuple_windows：相邻窗口\n    let diffs: Vec\u003Ci32> = v.iter().tuple_windows().map(|(a, b)| b - a).collect();\n\n    \u002F\u002F 7) cartesian_product：笛卡尔积\n    let pairs: Vec\u003C_> = (1..=2).cartesian_product('a'..='b').collect();\n    \u002F\u002F [(1,'a'), (1,'b'), (2,'a'), (2,'b')]\n\n    \u002F\u002F 8) minmax：一次遍历同时拿最小最大值\n    let (min, max) = v.iter().minmax().into_option().unwrap();\n\n    \u002F\u002F 9) zip_eq：长度必须相等的 zip（不等会 panic，用于防御长度不匹配的静默截断 bug）\n    let zipped: Vec\u003C_> = [1, 2, 3].iter().zip_eq(['a', 'b', 'c'].iter()).collect();\n\n    \u002F\u002F 10) format! 风格快速打印\n    println!(\"{:?} {:?} {:?} {:?} {:?} {:?} {:?}\", uniq, sorted, desc, chunks, groups, diffs, pairs);\n    println!(\"{s} | min={min} max={max} | {}\", zipped.iter().map(|(a,_)| a).join(\"+\"));\n}\n",[52,13888,13889,13903,13907,13917,13964,13968,13975,14021,14025,14032,14072,14135,14139,14146,14223,14230,14234,14241,14279,14332,14339,14343,14350,14383,14387,14394,14464,14468,14475,14533,14540,14544,14551,14596,14600,14607,14692,14696,14703,14785,14870],{"__ignoreMap":67},[71,13890,13891,13893,13896,13898,13901],{"class":73,"line":74},[71,13892,436],{"class":99},[71,13894,13895],{"class":439}," itertools",[71,13897,442],{"class":99},[71,13899,13900],{"class":439},"Itertools",[71,13902,687],{"class":445},[71,13904,13905],{"class":73,"line":85},[71,13906,464],{"emptyLinePlaceholder":463},[71,13908,13909,13911,13913,13915],{"class":73,"line":106},[71,13910,568],{"class":99},[71,13912,571],{"class":88},[71,13914,574],{"class":445},[71,13916,502],{"class":445},[71,13918,13919,13921,13923,13925,13927,13929,13931,13933,13935,13937,13939,13941,13943,13945,13948,13950,13953,13955,13957,13959,13962],{"class":73,"line":121},[71,13920,604],{"class":99},[71,13922,801],{"class":507},[71,13924,610],{"class":99},[71,13926,4160],{"class":88},[71,13928,1315],{"class":445},[71,13930,4432],{"class":88},[71,13932,452],{"class":445},[71,13934,4272],{"class":88},[71,13936,452],{"class":445},[71,13938,9360],{"class":88},[71,13940,452],{"class":445},[71,13942,4272],{"class":88},[71,13944,452],{"class":445},[71,13946,13947],{"class":88}," 5",[71,13949,452],{"class":445},[71,13951,13952],{"class":88}," 9",[71,13954,452],{"class":445},[71,13956,11217],{"class":88},[71,13958,452],{"class":445},[71,13960,13961],{"class":88}," 6",[71,13963,12414],{"class":445},[71,13965,13966],{"class":73,"line":259},[71,13967,464],{"emptyLinePlaceholder":463},[71,13969,13970,13972],{"class":73,"line":273},[71,13971,595],{"class":77},[71,13973,13974],{"class":81}," 1) unique：去重（保留顺序）\n",[71,13976,13977,13979,13982,13984,13986,13988,13990,13992,13994,13996,13998,14001,14003,14005,14008,14010,14012,14014,14016,14018],{"class":73,"line":286},[71,13978,604],{"class":99},[71,13980,13981],{"class":507}," uniq",[71,13983,511],{"class":99},[71,13985,966],{"class":439},[71,13987,542],{"class":445},[71,13989,3584],{"class":507},[71,13991,1730],{"class":445},[71,13993,610],{"class":99},[71,13995,801],{"class":507},[71,13997,626],{"class":99},[71,13999,14000],{"class":88},"iter",[71,14002,574],{"class":445},[71,14004,626],{"class":99},[71,14006,14007],{"class":88},"unique",[71,14009,574],{"class":445},[71,14011,626],{"class":99},[71,14013,12515],{"class":88},[71,14015,4305],{"class":445},[71,14017,551],{"class":77},[71,14019,14020],{"class":81}," [3, 1, 4, 5, 9, 2, 6]\n",[71,14022,14023],{"class":73,"line":300},[71,14024,464],{"emptyLinePlaceholder":463},[71,14026,14027,14029],{"class":73,"line":316},[71,14028,595],{"class":77},[71,14030,14031],{"class":81}," 2) sorted \u002F sorted_by：迭代器直接排序\n",[71,14033,14034,14036,14039,14041,14043,14045,14047,14049,14051,14053,14055,14057,14059,14061,14064,14066,14068,14070],{"class":73,"line":330},[71,14035,604],{"class":99},[71,14037,14038],{"class":507}," sorted",[71,14040,511],{"class":99},[71,14042,966],{"class":439},[71,14044,542],{"class":445},[71,14046,3584],{"class":507},[71,14048,1730],{"class":445},[71,14050,610],{"class":99},[71,14052,801],{"class":507},[71,14054,626],{"class":99},[71,14056,14000],{"class":88},[71,14058,574],{"class":445},[71,14060,626],{"class":99},[71,14062,14063],{"class":88},"sorted",[71,14065,574],{"class":445},[71,14067,626],{"class":99},[71,14069,12515],{"class":88},[71,14071,1499],{"class":445},[71,14073,14074,14076,14079,14081,14083,14085,14087,14089,14091,14093,14095,14097,14099,14101,14104,14106,14108,14110,14112,14114,14116,14118,14120,14123,14125,14127,14129,14131,14133],{"class":73,"line":352},[71,14075,604],{"class":99},[71,14077,14078],{"class":507}," desc",[71,14080,511],{"class":99},[71,14082,966],{"class":439},[71,14084,542],{"class":445},[71,14086,3584],{"class":507},[71,14088,1730],{"class":445},[71,14090,610],{"class":99},[71,14092,801],{"class":507},[71,14094,626],{"class":99},[71,14096,14000],{"class":88},[71,14098,574],{"class":445},[71,14100,626],{"class":99},[71,14102,14103],{"class":88},"sorted_by",[71,14105,475],{"class":445},[71,14107,7400],{"class":99},[71,14109,38],{"class":507},[71,14111,452],{"class":445},[71,14113,12200],{"class":507},[71,14115,7400],{"class":99},[71,14117,12200],{"class":507},[71,14119,626],{"class":99},[71,14121,14122],{"class":88},"cmp",[71,14124,475],{"class":445},[71,14126,38],{"class":507},[71,14128,1803],{"class":445},[71,14130,626],{"class":99},[71,14132,12515],{"class":88},[71,14134,1499],{"class":445},[71,14136,14137],{"class":73,"line":601},[71,14138,464],{"emptyLinePlaceholder":463},[71,14140,14141,14143],{"class":73,"line":656},[71,14142,595],{"class":77},[71,14144,14145],{"class":81}," 3) chunk \u002F chunks：分组\n",[71,14147,14148,14150,14153,14155,14157,14159,14161,14163,14165,14168,14170,14172,14174,14176,14178,14180,14183,14185,14187,14189,14191,14194,14196,14198,14200,14202,14204,14207,14209,14211,14213,14215,14217,14219,14221],{"class":73,"line":690},[71,14149,604],{"class":99},[71,14151,14152],{"class":507}," chunks",[71,14154,511],{"class":99},[71,14156,966],{"class":439},[71,14158,542],{"class":445},[71,14160,3066],{"class":439},[71,14162,542],{"class":445},[71,14164,3584],{"class":507},[71,14166,14167],{"class":445},">>",[71,14169,610],{"class":99},[71,14171,801],{"class":507},[71,14173,626],{"class":99},[71,14175,14000],{"class":88},[71,14177,574],{"class":445},[71,14179,626],{"class":99},[71,14181,14182],{"class":88},"chunks",[71,14184,475],{"class":445},[71,14186,4432],{"class":88},[71,14188,681],{"class":445},[71,14190,626],{"class":99},[71,14192,14193],{"class":88},"into_iter",[71,14195,574],{"class":445},[71,14197,626],{"class":99},[71,14199,12561],{"class":88},[71,14201,475],{"class":445},[71,14203,7400],{"class":99},[71,14205,14206],{"class":507},"c",[71,14208,7400],{"class":99},[71,14210,4190],{"class":507},[71,14212,626],{"class":99},[71,14214,12515],{"class":88},[71,14216,3044],{"class":445},[71,14218,626],{"class":99},[71,14220,12515],{"class":88},[71,14222,1499],{"class":445},[71,14224,14225,14227],{"class":73,"line":719},[71,14226,595],{"class":77},[71,14228,14229],{"class":81}," [[3,1,4], [1,5,9], [2,6]]\n",[71,14231,14232],{"class":73,"line":724},[71,14233,464],{"emptyLinePlaceholder":463},[71,14235,14236,14238],{"class":73,"line":732},[71,14237,595],{"class":77},[71,14239,14240],{"class":81}," 4) group_by：按 key 分组（类似 SQL GROUP BY）\n",[71,14242,14243,14245,14248,14250,14252,14254,14257,14259,14262,14264,14267,14269,14272,14274,14277],{"class":73,"line":768},[71,14244,604],{"class":99},[71,14246,14247],{"class":507}," words",[71,14249,610],{"class":99},[71,14251,4160],{"class":88},[71,14253,1315],{"class":445},[71,14255,14256],{"class":92},"\"apple\"",[71,14258,452],{"class":445},[71,14260,14261],{"class":92}," \"avocado\"",[71,14263,452],{"class":445},[71,14265,14266],{"class":92}," \"banana\"",[71,14268,452],{"class":445},[71,14270,14271],{"class":92}," \"blueberry\"",[71,14273,452],{"class":445},[71,14275,14276],{"class":92}," \"cherry\"",[71,14278,12414],{"class":445},[71,14280,14281,14283,14286,14288,14290,14292,14294,14296,14298,14301,14303,14305,14308,14310,14313,14315,14318,14320,14322,14324,14326,14328,14330],{"class":73,"line":773},[71,14282,604],{"class":99},[71,14284,14285],{"class":507}," groups",[71,14287,610],{"class":99},[71,14289,14247],{"class":507},[71,14291,626],{"class":99},[71,14293,14000],{"class":88},[71,14295,574],{"class":445},[71,14297,626],{"class":99},[71,14299,14300],{"class":88},"into_group_map_by",[71,14302,475],{"class":445},[71,14304,7400],{"class":99},[71,14306,14307],{"class":507},"w",[71,14309,7400],{"class":99},[71,14311,14312],{"class":507}," w",[71,14314,626],{"class":99},[71,14316,14317],{"class":88},"chars",[71,14319,574],{"class":445},[71,14321,626],{"class":99},[71,14323,5902],{"class":88},[71,14325,574],{"class":445},[71,14327,626],{"class":99},[71,14329,1496],{"class":88},[71,14331,2019],{"class":445},[71,14333,14334,14336],{"class":73,"line":781},[71,14335,595],{"class":77},[71,14337,14338],{"class":81}," {'a': [apple, avocado], 'b': [banana, blueberry], 'c': [cherry]}\n",[71,14340,14341],{"class":73,"line":796},[71,14342,464],{"emptyLinePlaceholder":463},[71,14344,14345,14347],{"class":73,"line":828},[71,14346,595],{"class":77},[71,14348,14349],{"class":81}," 5) join：迭代器直接拼字符串（比 collect 成 Vec 再 join 少一步）\n",[71,14351,14352,14354,14356,14358,14360,14362,14364,14366,14368,14371,14373,14376,14378,14380],{"class":73,"line":848},[71,14353,604],{"class":99},[71,14355,661],{"class":507},[71,14357,610],{"class":99},[71,14359,14247],{"class":507},[71,14361,626],{"class":99},[71,14363,14000],{"class":88},[71,14365,574],{"class":445},[71,14367,626],{"class":99},[71,14369,14370],{"class":88},"join",[71,14372,475],{"class":445},[71,14374,14375],{"class":92},"\", \"",[71,14377,711],{"class":445},[71,14379,551],{"class":77},[71,14381,14382],{"class":81}," \"apple, avocado, ...\"\n",[71,14384,14385],{"class":73,"line":857},[71,14386,464],{"emptyLinePlaceholder":463},[71,14388,14389,14391],{"class":73,"line":2022},[71,14390,595],{"class":77},[71,14392,14393],{"class":81}," 6) tuple_windows：相邻窗口\n",[71,14395,14396,14398,14401,14403,14405,14407,14410,14412,14414,14416,14418,14420,14422,14424,14427,14429,14431,14433,14435,14437,14439,14441,14443,14445,14447,14449,14451,14453,14456,14458,14460,14462],{"class":73,"line":2027},[71,14397,604],{"class":99},[71,14399,14400],{"class":507}," diffs",[71,14402,511],{"class":99},[71,14404,966],{"class":439},[71,14406,542],{"class":445},[71,14408,14409],{"class":439},"i32",[71,14411,1730],{"class":445},[71,14413,610],{"class":99},[71,14415,801],{"class":507},[71,14417,626],{"class":99},[71,14419,14000],{"class":88},[71,14421,574],{"class":445},[71,14423,626],{"class":99},[71,14425,14426],{"class":88},"tuple_windows",[71,14428,574],{"class":445},[71,14430,626],{"class":99},[71,14432,12561],{"class":88},[71,14434,475],{"class":445},[71,14436,7400],{"class":99},[71,14438,475],{"class":445},[71,14440,38],{"class":507},[71,14442,452],{"class":445},[71,14444,12200],{"class":507},[71,14446,681],{"class":445},[71,14448,7400],{"class":99},[71,14450,12200],{"class":507},[71,14452,11398],{"class":99},[71,14454,14455],{"class":507}," a",[71,14457,681],{"class":445},[71,14459,626],{"class":99},[71,14461,12515],{"class":88},[71,14463,1499],{"class":445},[71,14465,14466],{"class":73,"line":2035},[71,14467,464],{"emptyLinePlaceholder":463},[71,14469,14470,14472],{"class":73,"line":2068},[71,14471,595],{"class":77},[71,14473,14474],{"class":81}," 7) cartesian_product：笛卡尔积\n",[71,14476,14477,14479,14482,14484,14486,14488,14490,14492,14494,14496,14498,14500,14502,14504,14506,14509,14511,14513,14515,14517,14519,14521,14523,14525,14527,14529,14531],{"class":73,"line":2095},[71,14478,604],{"class":99},[71,14480,14481],{"class":507}," pairs",[71,14483,511],{"class":99},[71,14485,966],{"class":439},[71,14487,542],{"class":445},[71,14489,3584],{"class":507},[71,14491,1730],{"class":445},[71,14493,610],{"class":99},[71,14495,3162],{"class":445},[71,14497,2061],{"class":88},[71,14499,9395],{"class":99},[71,14501,13233],{"class":88},[71,14503,681],{"class":445},[71,14505,626],{"class":99},[71,14507,14508],{"class":88},"cartesian_product",[71,14510,475],{"class":445},[71,14512,3440],{"class":445},[71,14514,38],{"class":92},[71,14516,3440],{"class":445},[71,14518,9395],{"class":99},[71,14520,3440],{"class":445},[71,14522,12671],{"class":92},[71,14524,3440],{"class":445},[71,14526,681],{"class":445},[71,14528,626],{"class":99},[71,14530,12515],{"class":88},[71,14532,1499],{"class":445},[71,14534,14535,14537],{"class":73,"line":2131},[71,14536,595],{"class":77},[71,14538,14539],{"class":81}," [(1,'a'), (1,'b'), (2,'a'), (2,'b')]\n",[71,14541,14542],{"class":73,"line":2136},[71,14543,464],{"emptyLinePlaceholder":463},[71,14545,14546,14548],{"class":73,"line":2143},[71,14547,595],{"class":77},[71,14549,14550],{"class":81}," 8) minmax：一次遍历同时拿最小最大值\n",[71,14552,14553,14555,14557,14559,14561,14564,14566,14568,14570,14572,14574,14576,14578,14581,14583,14585,14588,14590,14592,14594],{"class":73,"line":4626},[71,14554,604],{"class":99},[71,14556,3162],{"class":445},[71,14558,5077],{"class":507},[71,14560,452],{"class":445},[71,14562,14563],{"class":507}," max",[71,14565,681],{"class":445},[71,14567,610],{"class":99},[71,14569,801],{"class":507},[71,14571,626],{"class":99},[71,14573,14000],{"class":88},[71,14575,574],{"class":445},[71,14577,626],{"class":99},[71,14579,14580],{"class":88},"minmax",[71,14582,574],{"class":445},[71,14584,626],{"class":99},[71,14586,14587],{"class":88},"into_option",[71,14589,574],{"class":445},[71,14591,626],{"class":99},[71,14593,1496],{"class":88},[71,14595,1499],{"class":445},[71,14597,14598],{"class":73,"line":4649},[71,14599,464],{"emptyLinePlaceholder":463},[71,14601,14602,14604],{"class":73,"line":4654},[71,14603,595],{"class":77},[71,14605,14606],{"class":81}," 9) zip_eq：长度必须相等的 zip（不等会 panic，用于防御长度不匹配的静默截断 bug）\n",[71,14608,14609,14611,14614,14616,14618,14620,14622,14624,14626,14629,14631,14633,14635,14637,14639,14641,14643,14645,14647,14649,14652,14655,14657,14659,14661,14663,14666,14668,14670,14672,14674,14676,14678,14680,14682,14684,14686,14688,14690],{"class":73,"line":4662},[71,14610,604],{"class":99},[71,14612,14613],{"class":507}," zipped",[71,14615,511],{"class":99},[71,14617,966],{"class":439},[71,14619,542],{"class":445},[71,14621,3584],{"class":507},[71,14623,1730],{"class":445},[71,14625,610],{"class":99},[71,14627,14628],{"class":445}," [",[71,14630,2061],{"class":88},[71,14632,452],{"class":445},[71,14634,11217],{"class":88},[71,14636,452],{"class":445},[71,14638,1421],{"class":88},[71,14640,1337],{"class":445},[71,14642,626],{"class":99},[71,14644,14000],{"class":88},[71,14646,574],{"class":445},[71,14648,626],{"class":99},[71,14650,14651],{"class":88},"zip_eq",[71,14653,14654],{"class":445},"([",[71,14656,3440],{"class":445},[71,14658,38],{"class":92},[71,14660,3440],{"class":445},[71,14662,452],{"class":445},[71,14664,14665],{"class":445}," '",[71,14667,12671],{"class":92},[71,14669,3440],{"class":445},[71,14671,452],{"class":445},[71,14673,14665],{"class":445},[71,14675,14206],{"class":92},[71,14677,3440],{"class":445},[71,14679,1337],{"class":445},[71,14681,626],{"class":99},[71,14683,14000],{"class":88},[71,14685,3044],{"class":445},[71,14687,626],{"class":99},[71,14689,12515],{"class":88},[71,14691,1499],{"class":445},[71,14693,14694],{"class":73,"line":4698},[71,14695,464],{"emptyLinePlaceholder":463},[71,14697,14698,14700],{"class":73,"line":4723},[71,14699,595],{"class":77},[71,14701,14702],{"class":81}," 10) format! 风格快速打印\n",[71,14704,14705,14707,14709,14711,14713,14715,14717,14719,14721,14723,14725,14727,14729,14731,14733,14735,14737,14739,14741,14743,14745,14747,14749,14751,14753,14755,14757,14759,14761,14763,14765,14767,14769,14771,14773,14775,14777,14779,14781,14783],{"class":73,"line":4741},[71,14706,693],{"class":88},[71,14708,475],{"class":445},[71,14710,698],{"class":92},[71,14712,446],{"class":445},[71,14714,2107],{"class":92},[71,14716,706],{"class":445},[71,14718,615],{"class":445},[71,14720,2107],{"class":92},[71,14722,706],{"class":445},[71,14724,615],{"class":445},[71,14726,2107],{"class":92},[71,14728,706],{"class":445},[71,14730,615],{"class":445},[71,14732,2107],{"class":92},[71,14734,706],{"class":445},[71,14736,615],{"class":445},[71,14738,2107],{"class":92},[71,14740,706],{"class":445},[71,14742,615],{"class":445},[71,14744,2107],{"class":92},[71,14746,706],{"class":445},[71,14748,615],{"class":445},[71,14750,2107],{"class":92},[71,14752,706],{"class":445},[71,14754,698],{"class":92},[71,14756,452],{"class":445},[71,14758,13981],{"class":507},[71,14760,452],{"class":445},[71,14762,14038],{"class":507},[71,14764,452],{"class":445},[71,14766,14078],{"class":507},[71,14768,452],{"class":445},[71,14770,14152],{"class":507},[71,14772,452],{"class":445},[71,14774,14285],{"class":507},[71,14776,452],{"class":445},[71,14778,14400],{"class":507},[71,14780,452],{"class":445},[71,14782,14481],{"class":507},[71,14784,765],{"class":445},[71,14786,14787,14789,14791,14793,14795,14797,14799,14802,14804,14806,14808,14811,14813,14816,14818,14821,14823,14825,14827,14829,14831,14833,14835,14837,14839,14841,14843,14845,14847,14849,14851,14853,14855,14857,14859,14861,14863,14865,14868],{"class":73,"line":4763},[71,14788,693],{"class":88},[71,14790,475],{"class":445},[71,14792,698],{"class":92},[71,14794,446],{"class":445},[71,14796,703],{"class":92},[71,14798,706],{"class":445},[71,14800,14801],{"class":92}," | min=",[71,14803,446],{"class":445},[71,14805,5077],{"class":92},[71,14807,706],{"class":445},[71,14809,14810],{"class":92}," max=",[71,14812,446],{"class":445},[71,14814,14815],{"class":92},"max",[71,14817,706],{"class":445},[71,14819,14820],{"class":92}," | ",[71,14822,741],{"class":445},[71,14824,698],{"class":92},[71,14826,452],{"class":445},[71,14828,14613],{"class":507},[71,14830,626],{"class":99},[71,14832,14000],{"class":88},[71,14834,574],{"class":445},[71,14836,626],{"class":99},[71,14838,12561],{"class":88},[71,14840,475],{"class":445},[71,14842,7400],{"class":99},[71,14844,475],{"class":445},[71,14846,38],{"class":507},[71,14848,452],{"class":445},[71,14850,3584],{"class":507},[71,14852,681],{"class":445},[71,14854,7400],{"class":99},[71,14856,14455],{"class":507},[71,14858,681],{"class":445},[71,14860,626],{"class":99},[71,14862,14370],{"class":88},[71,14864,475],{"class":445},[71,14866,14867],{"class":92},"\"+\"",[71,14869,4146],{"class":445},[71,14871,14872],{"class":73,"line":4787},[71,14873,559],{"class":445},[14,14875,14876,14879,14880,14883,14884,14887],{},[18,14877,14878],{},"为什么需要 itertools？"," 标准库刻意保持精简，这些操作每个项目都要自己写一遍（而且手写 ",[52,14881,14882],{},"group_by"," 很容易写错）。itertools 的 API 与标准库 Iterator trait 无缝组合——",[52,14885,14886],{},"use itertools::Itertools;"," 一行引入后，所有迭代器自动多出这些方法，零成本、零心智负担。",[23,14889],{},[26,14891,14893],{"id":14892},"第-12-章-rayon一行代码的数据并行","第 12 章 rayon：一行代码的数据并行",[57,14895,14897],{"id":14896},"_121-它解决什么问题","12.1 它解决什么问题",[14,14899,14900,14901,14904,14905,375],{},"CPU 密集型循环想利用多核。传统做法要手动开线程、切分数据、收结果；rayon 只需把 ",[52,14902,14903],{},"iter()"," 换成 ",[52,14906,14907],{},"par_iter()",[62,14909,14911],{"className":143,"code":14910,"language":145,"meta":67,"style":67},"[dependencies]\nrayon = \"1\"\n",[52,14912,14913,14917],{"__ignoreMap":67},[71,14914,14915],{"class":73,"line":74},[71,14916,152],{},[71,14918,14919],{"class":73,"line":85},[71,14920,14921],{},"rayon = \"1\"\n",[62,14923,14925],{"className":427,"code":14924,"language":429,"meta":67,"style":67},"use rayon::prelude::*;\n\nfn is_prime(n: u64) -> bool {\n    if n \u003C 2 { return false; }\n    (2..=((n as f64).sqrt() as u64)).all(|i| n % i != 0)\n}\n\nfn main() {\n    let nums: Vec\u003Cu64> = (1_000_000..2_000_000).collect();\n\n    \u002F\u002F 串行版\n    let count = nums.iter().filter(|&&n| is_prime(n)).count();\n\n    \u002F\u002F 并行版：iter -> par_iter，就这一个改动\n    let count_par = nums.par_iter().filter(|&&n| is_prime(n)).count();\n    assert_eq!(count, count_par);\n\n    \u002F\u002F 其他常见并行操作，API 与迭代器几乎一致\n    let sum: u64 = nums.par_iter().sum();                    \u002F\u002F 并行求和\n    let squares: Vec\u003Cu64> = nums.par_iter().map(|n| n * n).collect(); \u002F\u002F 并行 map 收集\n    let max = nums.par_iter().max();\n    nums.par_iter().for_each(|n| { \u002F* 只执行不收集 *\u002F let _ = n; });\n    println!(\"{sum} {} {max:?}\", squares.len());\n}\n",[52,14926,14927,14942,14946,14969,14991,15051,15055,15059,15069,15106,15110,15117,15163,15167,15174,15218,15233,15237,15244,15277,15335,15357,15398,15434],{"__ignoreMap":67},[71,14928,14929,14931,14934,14936,14938,14940],{"class":73,"line":74},[71,14930,436],{"class":99},[71,14932,14933],{"class":439}," rayon",[71,14935,442],{"class":99},[71,14937,12110],{"class":439},[71,14939,12113],{"class":99},[71,14941,687],{"class":445},[71,14943,14944],{"class":73,"line":85},[71,14945,464],{"emptyLinePlaceholder":463},[71,14947,14948,14950,14953,14955,14957,14959,14961,14963,14965,14967],{"class":73,"line":106},[71,14949,568],{"class":99},[71,14951,14952],{"class":88}," is_prime",[71,14954,475],{"class":445},[71,14956,4266],{"class":507},[71,14958,511],{"class":99},[71,14960,931],{"class":439},[71,14962,681],{"class":445},[71,14964,577],{"class":99},[71,14966,9614],{"class":439},[71,14968,502],{"class":445},[71,14970,14971,14973,14975,14978,14980,14982,14984,14987,14989],{"class":73,"line":121},[71,14972,1894],{"class":99},[71,14974,4238],{"class":507},[71,14976,14977],{"class":99}," \u003C",[71,14979,11217],{"class":88},[71,14981,615],{"class":445},[71,14983,2463],{"class":99},[71,14985,14986],{"class":88}," false",[71,14988,3029],{"class":445},[71,14990,1180],{"class":445},[71,14992,14993,14996,14998,15000,15003,15005,15008,15010,15012,15014,15017,15019,15021,15023,15025,15027,15030,15032,15034,15036,15038,15040,15042,15044,15047,15049],{"class":73,"line":259},[71,14994,14995],{"class":445},"    (",[71,14997,13233],{"class":88},[71,14999,9395],{"class":99},[71,15001,15002],{"class":445},"((",[71,15004,4266],{"class":507},[71,15006,15007],{"class":99}," as",[71,15009,12225],{"class":439},[71,15011,681],{"class":445},[71,15013,626],{"class":99},[71,15015,15016],{"class":88},"sqrt",[71,15018,574],{"class":445},[71,15020,15007],{"class":99},[71,15022,931],{"class":439},[71,15024,1803],{"class":445},[71,15026,626],{"class":99},[71,15028,15029],{"class":88},"all",[71,15031,475],{"class":445},[71,15033,7400],{"class":99},[71,15035,4459],{"class":507},[71,15037,7400],{"class":99},[71,15039,4238],{"class":507},[71,15041,10478],{"class":99},[71,15043,4423],{"class":507},[71,15045,15046],{"class":99}," !=",[71,15048,1868],{"class":88},[71,15050,1770],{"class":445},[71,15052,15053],{"class":73,"line":273},[71,15054,559],{"class":445},[71,15056,15057],{"class":73,"line":286},[71,15058,464],{"emptyLinePlaceholder":463},[71,15060,15061,15063,15065,15067],{"class":73,"line":300},[71,15062,568],{"class":99},[71,15064,571],{"class":88},[71,15066,574],{"class":445},[71,15068,502],{"class":445},[71,15070,15071,15073,15076,15078,15080,15082,15084,15086,15088,15090,15093,15095,15098,15100,15102,15104],{"class":73,"line":316},[71,15072,604],{"class":99},[71,15074,15075],{"class":507}," nums",[71,15077,511],{"class":99},[71,15079,966],{"class":439},[71,15081,542],{"class":445},[71,15083,2266],{"class":439},[71,15085,1730],{"class":445},[71,15087,610],{"class":99},[71,15089,3162],{"class":445},[71,15091,15092],{"class":88},"1_000_000",[71,15094,4178],{"class":99},[71,15096,15097],{"class":88},"2_000_000",[71,15099,681],{"class":445},[71,15101,626],{"class":99},[71,15103,12515],{"class":88},[71,15105,1499],{"class":445},[71,15107,15108],{"class":73,"line":330},[71,15109,464],{"emptyLinePlaceholder":463},[71,15111,15112,15114],{"class":73,"line":352},[71,15113,595],{"class":77},[71,15115,15116],{"class":81}," 串行版\n",[71,15118,15119,15121,15124,15126,15128,15130,15132,15134,15136,15139,15141,15144,15146,15148,15150,15152,15154,15156,15158,15161],{"class":73,"line":601},[71,15120,604],{"class":99},[71,15122,15123],{"class":507}," count",[71,15125,610],{"class":99},[71,15127,15075],{"class":507},[71,15129,626],{"class":99},[71,15131,14000],{"class":88},[71,15133,574],{"class":445},[71,15135,626],{"class":99},[71,15137,15138],{"class":88},"filter",[71,15140,475],{"class":445},[71,15142,15143],{"class":99},"|&&",[71,15145,4266],{"class":507},[71,15147,7400],{"class":99},[71,15149,14952],{"class":88},[71,15151,475],{"class":445},[71,15153,4266],{"class":507},[71,15155,1803],{"class":445},[71,15157,626],{"class":99},[71,15159,15160],{"class":88},"count",[71,15162,1499],{"class":445},[71,15164,15165],{"class":73,"line":656},[71,15166,464],{"emptyLinePlaceholder":463},[71,15168,15169,15171],{"class":73,"line":690},[71,15170,595],{"class":77},[71,15172,15173],{"class":81}," 并行版：iter -> par_iter，就这一个改动\n",[71,15175,15176,15178,15181,15183,15185,15187,15190,15192,15194,15196,15198,15200,15202,15204,15206,15208,15210,15212,15214,15216],{"class":73,"line":719},[71,15177,604],{"class":99},[71,15179,15180],{"class":507}," count_par",[71,15182,610],{"class":99},[71,15184,15075],{"class":507},[71,15186,626],{"class":99},[71,15188,15189],{"class":88},"par_iter",[71,15191,574],{"class":445},[71,15193,626],{"class":99},[71,15195,15138],{"class":88},[71,15197,475],{"class":445},[71,15199,15143],{"class":99},[71,15201,4266],{"class":507},[71,15203,7400],{"class":99},[71,15205,14952],{"class":88},[71,15207,475],{"class":445},[71,15209,4266],{"class":507},[71,15211,1803],{"class":445},[71,15213,626],{"class":99},[71,15215,15160],{"class":88},[71,15217,1499],{"class":445},[71,15219,15220,15223,15225,15227,15229,15231],{"class":73,"line":724},[71,15221,15222],{"class":88},"    assert_eq!",[71,15224,475],{"class":445},[71,15226,15160],{"class":507},[71,15228,452],{"class":445},[71,15230,15180],{"class":507},[71,15232,765],{"class":445},[71,15234,15235],{"class":73,"line":732},[71,15236,464],{"emptyLinePlaceholder":463},[71,15238,15239,15241],{"class":73,"line":768},[71,15240,595],{"class":77},[71,15242,15243],{"class":81}," 其他常见并行操作，API 与迭代器几乎一致\n",[71,15245,15246,15248,15251,15253,15255,15257,15259,15261,15263,15265,15267,15270,15272,15274],{"class":73,"line":773},[71,15247,604],{"class":99},[71,15249,15250],{"class":507}," sum",[71,15252,511],{"class":99},[71,15254,931],{"class":439},[71,15256,610],{"class":99},[71,15258,15075],{"class":507},[71,15260,626],{"class":99},[71,15262,15189],{"class":88},[71,15264,574],{"class":445},[71,15266,626],{"class":99},[71,15268,15269],{"class":88},"sum",[71,15271,4305],{"class":445},[71,15273,5349],{"class":77},[71,15275,15276],{"class":81}," 并行求和\n",[71,15278,15279,15281,15284,15286,15288,15290,15292,15294,15296,15298,15300,15302,15304,15306,15308,15310,15312,15314,15316,15318,15320,15322,15324,15326,15328,15330,15332],{"class":73,"line":781},[71,15280,604],{"class":99},[71,15282,15283],{"class":507}," squares",[71,15285,511],{"class":99},[71,15287,966],{"class":439},[71,15289,542],{"class":445},[71,15291,2266],{"class":439},[71,15293,1730],{"class":445},[71,15295,610],{"class":99},[71,15297,15075],{"class":507},[71,15299,626],{"class":99},[71,15301,15189],{"class":88},[71,15303,574],{"class":445},[71,15305,626],{"class":99},[71,15307,12561],{"class":88},[71,15309,475],{"class":445},[71,15311,7400],{"class":99},[71,15313,4266],{"class":507},[71,15315,7400],{"class":99},[71,15317,4238],{"class":507},[71,15319,4324],{"class":99},[71,15321,4238],{"class":507},[71,15323,681],{"class":445},[71,15325,626],{"class":99},[71,15327,12515],{"class":88},[71,15329,4305],{"class":445},[71,15331,551],{"class":77},[71,15333,15334],{"class":81}," 并行 map 收集\n",[71,15336,15337,15339,15341,15343,15345,15347,15349,15351,15353,15355],{"class":73,"line":796},[71,15338,604],{"class":99},[71,15340,14563],{"class":507},[71,15342,610],{"class":99},[71,15344,15075],{"class":507},[71,15346,626],{"class":99},[71,15348,15189],{"class":88},[71,15350,574],{"class":445},[71,15352,626],{"class":99},[71,15354,14815],{"class":88},[71,15356,1499],{"class":445},[71,15358,15359,15362,15364,15366,15368,15370,15373,15375,15377,15379,15381,15383,15386,15388,15390,15392,15394,15396],{"class":73,"line":828},[71,15360,15361],{"class":507},"    nums",[71,15363,626],{"class":99},[71,15365,15189],{"class":88},[71,15367,574],{"class":445},[71,15369,626],{"class":99},[71,15371,15372],{"class":88},"for_each",[71,15374,475],{"class":445},[71,15376,7400],{"class":99},[71,15378,4266],{"class":507},[71,15380,7400],{"class":99},[71,15382,615],{"class":445},[71,15384,15385],{"class":81}," \u002F* 只执行不收集 *\u002F",[71,15387,1298],{"class":99},[71,15389,3350],{"class":507},[71,15391,610],{"class":99},[71,15393,4238],{"class":507},[71,15395,3029],{"class":445},[71,15397,4623],{"class":445},[71,15399,15400,15402,15404,15406,15408,15410,15412,15415,15417,15420,15422,15424,15426,15428,15430,15432],{"class":73,"line":848},[71,15401,693],{"class":88},[71,15403,475],{"class":445},[71,15405,698],{"class":92},[71,15407,446],{"class":445},[71,15409,15269],{"class":92},[71,15411,706],{"class":445},[71,15413,15414],{"class":445}," {}",[71,15416,615],{"class":445},[71,15418,15419],{"class":92},"max:?",[71,15421,706],{"class":445},[71,15423,698],{"class":92},[71,15425,452],{"class":445},[71,15427,15283],{"class":507},[71,15429,626],{"class":99},[71,15431,2016],{"class":88},[71,15433,2019],{"class":445},[71,15435,15436],{"class":73,"line":857},[71,15437,559],{"class":445},[14,15439,15440,15443,15444,15447,15448,15451,15452,15454,15455,15458,15459,15462],{},[18,15441,15442],{},"为什么 rayon 敢让你无脑换？"," 两个设计：一是",[18,15445,15446],{},"工作窃取线程池","，任务自动均衡到所有核；二是",[18,15449,15450],{},"复用 Rust 的所有权系统","——",[52,15453,15189],{}," 要求闭包 ",[52,15456,15457],{},"Sync","\u002F",[52,15460,15461],{},"Send","，数据竞争在编译期就是错误，所以并行化不会引入并发 bug。这就是 Rust 名言「无畏并发（fearless concurrency）」的最佳例证。",[57,15464,15466,15467],{"id":15465},"_122-两个独立任务的并行join","12.2 两个独立任务的并行：",[52,15468,14370],{},[62,15470,15472],{"className":427,"code":15471,"language":429,"meta":67,"style":67},"fn heavy_a() -> u64 { 1 }\nfn heavy_b() -> u64 { 2 }\n\nlet (a, b) = rayon::join(heavy_a, heavy_b); \u002F\u002F 两个闭包可能并行执行\nprintln!(\"{a} {b}\");\n",[52,15473,15474,15493,15512,15516,15554],{"__ignoreMap":67},[71,15475,15476,15478,15481,15483,15485,15487,15489,15491],{"class":73,"line":74},[71,15477,568],{"class":99},[71,15479,15480],{"class":88}," heavy_a",[71,15482,574],{"class":445},[71,15484,577],{"class":99},[71,15486,931],{"class":439},[71,15488,615],{"class":445},[71,15490,4272],{"class":88},[71,15492,1180],{"class":445},[71,15494,15495,15497,15500,15502,15504,15506,15508,15510],{"class":73,"line":85},[71,15496,568],{"class":99},[71,15498,15499],{"class":88}," heavy_b",[71,15501,574],{"class":445},[71,15503,577],{"class":99},[71,15505,931],{"class":439},[71,15507,615],{"class":445},[71,15509,11217],{"class":88},[71,15511,1180],{"class":445},[71,15513,15514],{"class":73,"line":106},[71,15515,464],{"emptyLinePlaceholder":463},[71,15517,15518,15520,15522,15524,15526,15528,15530,15532,15534,15536,15538,15540,15543,15545,15547,15549,15551],{"class":73,"line":121},[71,15519,1241],{"class":99},[71,15521,3162],{"class":445},[71,15523,38],{"class":507},[71,15525,452],{"class":445},[71,15527,12200],{"class":507},[71,15529,681],{"class":445},[71,15531,610],{"class":99},[71,15533,14933],{"class":439},[71,15535,442],{"class":99},[71,15537,14370],{"class":88},[71,15539,475],{"class":445},[71,15541,15542],{"class":507},"heavy_a",[71,15544,452],{"class":445},[71,15546,15499],{"class":507},[71,15548,711],{"class":445},[71,15550,551],{"class":77},[71,15552,15553],{"class":81}," 两个闭包可能并行执行\n",[71,15555,15556,15558,15560,15562,15564,15566,15568,15570,15572,15574,15576],{"class":73,"line":259},[71,15557,10086],{"class":88},[71,15559,475],{"class":445},[71,15561,698],{"class":92},[71,15563,446],{"class":445},[71,15565,38],{"class":92},[71,15567,706],{"class":445},[71,15569,615],{"class":445},[71,15571,12671],{"class":92},[71,15573,706],{"class":445},[71,15575,698],{"class":92},[71,15577,765],{"class":445},[57,15579,15581],{"id":15580},"_123-何时不该用-rayon","12.3 何时不该用 rayon",[15583,15584,15585,15592,15598],"ul",{},[15586,15587,15588,15591],"li",{},[18,15589,15590],{},"数据量小","（几百个元素）：并行调度开销大于收益，串行更快。",[15586,15593,15594,15597],{},[18,15595,15596],{},"IO 密集","（网络\u002F磁盘）：那不是 CPU 瓶颈，该用 tokio 异步而不是线程并行。",[15586,15599,15600,15601,15604,15605,15608],{},"经验法则：",[18,15602,15603],{},"CPU 密集 + 单机 + 毫秒级以上的循环"," → rayon；",[18,15606,15607],{},"IO 密集 + 高并发连接"," → tokio。",[23,15610],{},[26,15612,15614],{"id":15613},"第-13-章-sqlx编译期检查的-sql","第 13 章 sqlx：编译期检查的 SQL",[57,15616,15618],{"id":15617},"_131-它解决什么问题","13.1 它解决什么问题",[14,15620,15621,15622,15633],{},"Rust 数据库生态两大路线：",[18,15623,15624,15625,15632],{},"ORM（如 diesel、sea-orm）",[18,15626,15627,15628,15631],{},"用代码生成 SQL；",[18,15629,15630],{},"sqlx"," 让你直接写 SQL，但用宏在","编译期连接真实数据库校验语句和类型","——SQL 写错了编译就过不去，兼得原生 SQL 的灵活和类型安全。",[62,15635,15637],{"className":143,"code":15636,"language":145,"meta":67,"style":67},"[dependencies]\nsqlx = { version = \"0.8\", features = [\"runtime-tokio\", \"mysql\", \"chrono\"] }\ntokio = { version = \"1\", features = [\"full\"] }\nchrono = \"0.4\"\n",[52,15638,15639,15643,15648,15652],{"__ignoreMap":67},[71,15640,15641],{"class":73,"line":74},[71,15642,152],{},[71,15644,15645],{"class":73,"line":85},[71,15646,15647],{},"sqlx = { version = \"0.8\", features = [\"runtime-tokio\", \"mysql\", \"chrono\"] }\n",[71,15649,15650],{"class":73,"line":106},[71,15651,162],{},[71,15653,15654],{"class":73,"line":121},[71,15655,15656],{},"chrono = \"0.4\"\n",[57,15658,15660],{"id":15659},"_132-连接池与基本-crud","13.2 连接池与基本 CRUD",[62,15662,15664],{"className":427,"code":15663,"language":429,"meta":67,"style":67},"use sqlx::MySqlPool;\n\n#[derive(sqlx::FromRow, Debug)]       \u002F\u002F FromRow：把查询结果行映射到结构体\nstruct User {\n    id: i64,\n    name: String,\n    email: Option\u003CString>,\n}\n\n#[tokio::main]\nasync fn main() -> Result\u003C(), sqlx::Error> {\n    \u002F\u002F 连接池：为什么必须用池？建一次 TCP+认证握手要几十毫秒，\n    \u002F\u002F 每个请求新建连接数据库会被打爆。池复用连接，自动处理断线重连。\n    let pool = MySqlPool::connect(\"mysql:\u002F\u002Fuser:pass@localhost\u002Fmydb\").await?;\n\n    \u002F\u002F 建表（演示用，生产用 sqlx migrate 管理迁移）\n    sqlx::query(\"CREATE TABLE IF NOT EXISTS user (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), email VARCHAR(100))\")\n        .execute(&pool).await?;\n\n    \u002F\u002F 1) 插入：? 占位符绑定参数，杜绝 SQL 注入\n    \u002F\u002F 为什么不拼接字符串？format!(\"...WHERE id = {id}\") 在 id 来自用户输入时就是注入漏洞\n    let r = sqlx::query(\"INSERT INTO user (name, email) VALUES (?, ?)\")\n        .bind(\"小明\")\n        .bind(\"xm@example.com\")\n        .execute(&pool).await?;\n    println!(\"插入 id = {}\", r.last_insert_id());\n\n    \u002F\u002F 2) 查询单条映射到结构体\n    let user = sqlx::query_as::\u003C_, User>(\"SELECT id, name, email FROM user WHERE id = ?\")\n        .bind(1i64)\n        .fetch_one(&pool).await?;      \u002F\u002F 没有行时返回 Err(RowNotFound)\n    println!(\"{user:?}\");\n\n    \u002F\u002F 3) 查询多条\n    let users: Vec\u003CUser> = sqlx::query_as(\"SELECT id, name, email FROM user WHERE name LIKE ?\")\n        .bind(\"小%\")\n        .fetch_all(&pool).await?;\n    println!(\"共 {} 人\", users.len());\n\n    \u002F\u002F 4) fetch_optional：行可能不存在时\n    let maybe: Option\u003CUser> = sqlx::query_as(\"SELECT id, name, email FROM user WHERE id = ?\")\n        .bind(999i64)\n        .fetch_optional(&pool).await?;\n    println!(\"{maybe:?}\");\n\n    \u002F\u002F 5) 更新 \u002F 删除：看受影响行数\n    let r = sqlx::query(\"UPDATE user SET email = ? WHERE id = ?\")\n        .bind(\"new@example.com\").bind(1i64)\n        .execute(&pool).await?;\n    println!(\"更新 {} 行\", r.rows_affected());\n\n    \u002F\u002F 6) 事务：要么全成，要么全回滚。转账类操作的底线\n    let mut tx = pool.begin().await?;\n    sqlx::query(\"UPDATE account SET balance = balance - 100 WHERE id = 1\")\n        .execute(&mut *tx).await?;\n    sqlx::query(\"UPDATE account SET balance = balance + 100 WHERE id = 2\")\n        .execute(&mut *tx).await?;\n    tx.commit().await?;   \u002F\u002F 若中途 panic 或提前返回，tx 被 drop 自动回滚\n    Ok(())\n}\n",[52,15665,15666,15679,15683,15709,15717,15729,15739,15753,15757,15761,15773,15800,15807,15814,15842,15846,15853,15869,15889,15893,15900,15907,15928,15940,15953,15971,15995,15999,16006,16039,16054,16078,16097,16101,16108,16141,16154,16173,16197,16201,16208,16239,16254,16273,16292,16296,16303,16324,16349,16367,16392,16396,16403,16426,16441,16461,16476,16496,16518,16525],{"__ignoreMap":67},[71,15667,15668,15670,15672,15674,15677],{"class":73,"line":74},[71,15669,436],{"class":99},[71,15671,2391],{"class":439},[71,15673,442],{"class":99},[71,15675,15676],{"class":439},"MySqlPool",[71,15678,687],{"class":445},[71,15680,15681],{"class":73,"line":85},[71,15682,464],{"emptyLinePlaceholder":463},[71,15684,15685,15687,15689,15691,15693,15695,15698,15700,15702,15704,15706],{"class":73,"line":106},[71,15686,469],{"class":445},[71,15688,176],{"class":472},[71,15690,475],{"class":445},[71,15692,15630],{"class":472},[71,15694,442],{"class":99},[71,15696,15697],{"class":439},"FromRow",[71,15699,452],{"class":445},[71,15701,488],{"class":439},[71,15703,915],{"class":445},[71,15705,6468],{"class":77},[71,15707,15708],{"class":81}," FromRow：把查询结果行映射到结构体\n",[71,15710,15711,15713,15715],{"class":73,"line":121},[71,15712,496],{"class":99},[71,15714,499],{"class":439},[71,15716,502],{"class":445},[71,15718,15719,15722,15724,15727],{"class":73,"line":259},[71,15720,15721],{"class":507},"    id",[71,15723,511],{"class":99},[71,15725,15726],{"class":439}," i64",[71,15728,517],{"class":445},[71,15730,15731,15733,15735,15737],{"class":73,"line":273},[71,15732,508],{"class":507},[71,15734,511],{"class":99},[71,15736,514],{"class":439},[71,15738,517],{"class":445},[71,15740,15741,15743,15745,15747,15749,15751],{"class":73,"line":286},[71,15742,534],{"class":507},[71,15744,511],{"class":99},[71,15746,539],{"class":439},[71,15748,542],{"class":445},[71,15750,545],{"class":439},[71,15752,973],{"class":445},[71,15754,15755],{"class":73,"line":300},[71,15756,559],{"class":445},[71,15758,15759],{"class":73,"line":316},[71,15760,464],{"emptyLinePlaceholder":463},[71,15762,15763,15765,15767,15769,15771],{"class":73,"line":330},[71,15764,469],{"class":445},[71,15766,2777],{"class":472},[71,15768,442],{"class":99},[71,15770,2782],{"class":472},[71,15772,2785],{"class":445},[71,15774,15775,15777,15779,15781,15783,15785,15787,15790,15792,15794,15796,15798],{"class":73,"line":352},[71,15776,2790],{"class":99},[71,15778,2793],{"class":99},[71,15780,571],{"class":88},[71,15782,574],{"class":445},[71,15784,577],{"class":99},[71,15786,1661],{"class":439},[71,15788,15789],{"class":445},"\u003C(),",[71,15791,2391],{"class":507},[71,15793,442],{"class":99},[71,15795,2192],{"class":439},[71,15797,1730],{"class":445},[71,15799,502],{"class":445},[71,15801,15802,15804],{"class":73,"line":601},[71,15803,595],{"class":77},[71,15805,15806],{"class":81}," 连接池：为什么必须用池？建一次 TCP+认证握手要几十毫秒，\n",[71,15808,15809,15811],{"class":73,"line":656},[71,15810,595],{"class":77},[71,15812,15813],{"class":81}," 每个请求新建连接数据库会被打爆。池复用连接，自动处理断线重连。\n",[71,15815,15816,15818,15821,15823,15826,15828,15831,15833,15836,15838,15840],{"class":73,"line":690},[71,15817,604],{"class":99},[71,15819,15820],{"class":507}," pool",[71,15822,610],{"class":99},[71,15824,15825],{"class":439}," MySqlPool",[71,15827,442],{"class":99},[71,15829,15830],{"class":88},"connect",[71,15832,475],{"class":445},[71,15834,15835],{"class":92},"\"mysql:\u002F\u002Fuser:pass@localhost\u002Fmydb\"",[71,15837,681],{"class":445},[71,15839,3736],{"class":99},[71,15841,687],{"class":445},[71,15843,15844],{"class":73,"line":719},[71,15845,464],{"emptyLinePlaceholder":463},[71,15847,15848,15850],{"class":73,"line":724},[71,15849,595],{"class":77},[71,15851,15852],{"class":81}," 建表（演示用，生产用 sqlx migrate 管理迁移）\n",[71,15854,15855,15858,15860,15862,15864,15867],{"class":73,"line":732},[71,15856,15857],{"class":439},"    sqlx",[71,15859,442],{"class":99},[71,15861,5529],{"class":88},[71,15863,475],{"class":445},[71,15865,15866],{"class":92},"\"CREATE TABLE IF NOT EXISTS user (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), email VARCHAR(100))\"",[71,15868,1770],{"class":445},[71,15870,15871,15873,15876,15878,15880,15883,15885,15887],{"class":73,"line":768},[71,15872,1775],{"class":99},[71,15874,15875],{"class":88},"execute",[71,15877,475],{"class":445},[71,15879,675],{"class":99},[71,15881,15882],{"class":507},"pool",[71,15884,681],{"class":445},[71,15886,3736],{"class":99},[71,15888,687],{"class":445},[71,15890,15891],{"class":73,"line":773},[71,15892,464],{"emptyLinePlaceholder":463},[71,15894,15895,15897],{"class":73,"line":781},[71,15896,595],{"class":77},[71,15898,15899],{"class":81}," 1) 插入：? 占位符绑定参数，杜绝 SQL 注入\n",[71,15901,15902,15904],{"class":73,"line":796},[71,15903,595],{"class":77},[71,15905,15906],{"class":81}," 为什么不拼接字符串？format!(\"...WHERE id = {id}\") 在 id 来自用户输入时就是注入漏洞\n",[71,15908,15909,15911,15913,15915,15917,15919,15921,15923,15926],{"class":73,"line":828},[71,15910,604],{"class":99},[71,15912,3209],{"class":507},[71,15914,610],{"class":99},[71,15916,2391],{"class":439},[71,15918,442],{"class":99},[71,15920,5529],{"class":88},[71,15922,475],{"class":445},[71,15924,15925],{"class":92},"\"INSERT INTO user (name, email) VALUES (?, ?)\"",[71,15927,1770],{"class":445},[71,15929,15930,15932,15934,15936,15938],{"class":73,"line":848},[71,15931,1775],{"class":99},[71,15933,6774],{"class":88},[71,15935,475],{"class":445},[71,15937,2493],{"class":92},[71,15939,1770],{"class":445},[71,15941,15942,15944,15946,15948,15951],{"class":73,"line":857},[71,15943,1775],{"class":99},[71,15945,6774],{"class":88},[71,15947,475],{"class":445},[71,15949,15950],{"class":92},"\"xm@example.com\"",[71,15952,1770],{"class":445},[71,15954,15955,15957,15959,15961,15963,15965,15967,15969],{"class":73,"line":2022},[71,15956,1775],{"class":99},[71,15958,15875],{"class":88},[71,15960,475],{"class":445},[71,15962,675],{"class":99},[71,15964,15882],{"class":507},[71,15966,681],{"class":445},[71,15968,3736],{"class":99},[71,15970,687],{"class":445},[71,15972,15973,15975,15977,15980,15982,15984,15986,15988,15990,15993],{"class":73,"line":2027},[71,15974,693],{"class":88},[71,15976,475],{"class":445},[71,15978,15979],{"class":92},"\"插入 id = ",[71,15981,741],{"class":445},[71,15983,698],{"class":92},[71,15985,452],{"class":445},[71,15987,3209],{"class":507},[71,15989,626],{"class":99},[71,15991,15992],{"class":88},"last_insert_id",[71,15994,2019],{"class":445},[71,15996,15997],{"class":73,"line":2035},[71,15998,464],{"emptyLinePlaceholder":463},[71,16000,16001,16003],{"class":73,"line":2068},[71,16002,595],{"class":77},[71,16004,16005],{"class":81}," 2) 查询单条映射到结构体\n",[71,16007,16008,16010,16013,16015,16017,16019,16022,16024,16026,16028,16030,16032,16034,16037],{"class":73,"line":2095},[71,16009,604],{"class":99},[71,16011,16012],{"class":507}," user",[71,16014,610],{"class":99},[71,16016,2391],{"class":439},[71,16018,442],{"class":99},[71,16020,16021],{"class":88},"query_as",[71,16023,442],{"class":99},[71,16025,542],{"class":445},[71,16027,3584],{"class":507},[71,16029,452],{"class":445},[71,16031,499],{"class":439},[71,16033,4389],{"class":445},[71,16035,16036],{"class":92},"\"SELECT id, name, email FROM user WHERE id = ?\"",[71,16038,1770],{"class":445},[71,16040,16041,16043,16045,16047,16049,16052],{"class":73,"line":2131},[71,16042,1775],{"class":99},[71,16044,6774],{"class":88},[71,16046,475],{"class":445},[71,16048,2061],{"class":88},[71,16050,16051],{"class":439},"i64",[71,16053,1770],{"class":445},[71,16055,16056,16058,16061,16063,16065,16067,16069,16071,16073,16075],{"class":73,"line":2136},[71,16057,1775],{"class":99},[71,16059,16060],{"class":88},"fetch_one",[71,16062,475],{"class":445},[71,16064,675],{"class":99},[71,16066,15882],{"class":507},[71,16068,681],{"class":445},[71,16070,3736],{"class":99},[71,16072,3029],{"class":445},[71,16074,9015],{"class":77},[71,16076,16077],{"class":81}," 没有行时返回 Err(RowNotFound)\n",[71,16079,16080,16082,16084,16086,16088,16091,16093,16095],{"class":73,"line":2143},[71,16081,693],{"class":88},[71,16083,475],{"class":445},[71,16085,698],{"class":92},[71,16087,446],{"class":445},[71,16089,16090],{"class":92},"user:?",[71,16092,706],{"class":445},[71,16094,698],{"class":92},[71,16096,765],{"class":445},[71,16098,16099],{"class":73,"line":4626},[71,16100,464],{"emptyLinePlaceholder":463},[71,16102,16103,16105],{"class":73,"line":4649},[71,16104,595],{"class":77},[71,16106,16107],{"class":81}," 3) 查询多条\n",[71,16109,16110,16112,16115,16117,16119,16121,16124,16126,16128,16130,16132,16134,16136,16139],{"class":73,"line":4654},[71,16111,604],{"class":99},[71,16113,16114],{"class":507}," users",[71,16116,511],{"class":99},[71,16118,966],{"class":439},[71,16120,542],{"class":445},[71,16122,16123],{"class":439},"User",[71,16125,1730],{"class":445},[71,16127,610],{"class":99},[71,16129,2391],{"class":439},[71,16131,442],{"class":99},[71,16133,16021],{"class":88},[71,16135,475],{"class":445},[71,16137,16138],{"class":92},"\"SELECT id, name, email FROM user WHERE name LIKE ?\"",[71,16140,1770],{"class":445},[71,16142,16143,16145,16147,16149,16152],{"class":73,"line":4662},[71,16144,1775],{"class":99},[71,16146,6774],{"class":88},[71,16148,475],{"class":445},[71,16150,16151],{"class":92},"\"小%\"",[71,16153,1770],{"class":445},[71,16155,16156,16158,16161,16163,16165,16167,16169,16171],{"class":73,"line":4698},[71,16157,1775],{"class":99},[71,16159,16160],{"class":88},"fetch_all",[71,16162,475],{"class":445},[71,16164,675],{"class":99},[71,16166,15882],{"class":507},[71,16168,681],{"class":445},[71,16170,3736],{"class":99},[71,16172,687],{"class":445},[71,16174,16175,16177,16179,16182,16184,16187,16189,16191,16193,16195],{"class":73,"line":4723},[71,16176,693],{"class":88},[71,16178,475],{"class":445},[71,16180,16181],{"class":92},"\"共 ",[71,16183,741],{"class":445},[71,16185,16186],{"class":92}," 人\"",[71,16188,452],{"class":445},[71,16190,16114],{"class":507},[71,16192,626],{"class":99},[71,16194,2016],{"class":88},[71,16196,2019],{"class":445},[71,16198,16199],{"class":73,"line":4741},[71,16200,464],{"emptyLinePlaceholder":463},[71,16202,16203,16205],{"class":73,"line":4763},[71,16204,595],{"class":77},[71,16206,16207],{"class":81}," 4) fetch_optional：行可能不存在时\n",[71,16209,16210,16212,16215,16217,16219,16221,16223,16225,16227,16229,16231,16233,16235,16237],{"class":73,"line":4787},[71,16211,604],{"class":99},[71,16213,16214],{"class":507}," maybe",[71,16216,511],{"class":99},[71,16218,539],{"class":439},[71,16220,542],{"class":445},[71,16222,16123],{"class":439},[71,16224,1730],{"class":445},[71,16226,610],{"class":99},[71,16228,2391],{"class":439},[71,16230,442],{"class":99},[71,16232,16021],{"class":88},[71,16234,475],{"class":445},[71,16236,16036],{"class":92},[71,16238,1770],{"class":445},[71,16240,16241,16243,16245,16247,16250,16252],{"class":73,"line":7646},[71,16242,1775],{"class":99},[71,16244,6774],{"class":88},[71,16246,475],{"class":445},[71,16248,16249],{"class":88},"999",[71,16251,16051],{"class":439},[71,16253,1770],{"class":445},[71,16255,16256,16258,16261,16263,16265,16267,16269,16271],{"class":73,"line":7659},[71,16257,1775],{"class":99},[71,16259,16260],{"class":88},"fetch_optional",[71,16262,475],{"class":445},[71,16264,675],{"class":99},[71,16266,15882],{"class":507},[71,16268,681],{"class":445},[71,16270,3736],{"class":99},[71,16272,687],{"class":445},[71,16274,16275,16277,16279,16281,16283,16286,16288,16290],{"class":73,"line":7672},[71,16276,693],{"class":88},[71,16278,475],{"class":445},[71,16280,698],{"class":92},[71,16282,446],{"class":445},[71,16284,16285],{"class":92},"maybe:?",[71,16287,706],{"class":445},[71,16289,698],{"class":92},[71,16291,765],{"class":445},[71,16293,16294],{"class":73,"line":7701},[71,16295,464],{"emptyLinePlaceholder":463},[71,16297,16298,16300],{"class":73,"line":7719},[71,16299,595],{"class":77},[71,16301,16302],{"class":81}," 5) 更新 \u002F 删除：看受影响行数\n",[71,16304,16305,16307,16309,16311,16313,16315,16317,16319,16322],{"class":73,"line":7725},[71,16306,604],{"class":99},[71,16308,3209],{"class":507},[71,16310,610],{"class":99},[71,16312,2391],{"class":439},[71,16314,442],{"class":99},[71,16316,5529],{"class":88},[71,16318,475],{"class":445},[71,16320,16321],{"class":92},"\"UPDATE user SET email = ? WHERE id = ?\"",[71,16323,1770],{"class":445},[71,16325,16326,16328,16330,16332,16335,16337,16339,16341,16343,16345,16347],{"class":73,"line":7730},[71,16327,1775],{"class":99},[71,16329,6774],{"class":88},[71,16331,475],{"class":445},[71,16333,16334],{"class":92},"\"new@example.com\"",[71,16336,681],{"class":445},[71,16338,626],{"class":99},[71,16340,6774],{"class":88},[71,16342,475],{"class":445},[71,16344,2061],{"class":88},[71,16346,16051],{"class":439},[71,16348,1770],{"class":445},[71,16350,16351,16353,16355,16357,16359,16361,16363,16365],{"class":73,"line":7747},[71,16352,1775],{"class":99},[71,16354,15875],{"class":88},[71,16356,475],{"class":445},[71,16358,675],{"class":99},[71,16360,15882],{"class":507},[71,16362,681],{"class":445},[71,16364,3736],{"class":99},[71,16366,687],{"class":445},[71,16368,16369,16371,16373,16376,16378,16381,16383,16385,16387,16390],{"class":73,"line":7783},[71,16370,693],{"class":88},[71,16372,475],{"class":445},[71,16374,16375],{"class":92},"\"更新 ",[71,16377,741],{"class":445},[71,16379,16380],{"class":92}," 行\"",[71,16382,452],{"class":445},[71,16384,3209],{"class":507},[71,16386,626],{"class":99},[71,16388,16389],{"class":88},"rows_affected",[71,16391,2019],{"class":445},[71,16393,16394],{"class":73,"line":7805},[71,16395,464],{"emptyLinePlaceholder":463},[71,16397,16398,16400],{"class":73,"line":7828},[71,16399,595],{"class":77},[71,16401,16402],{"class":81}," 6) 事务：要么全成，要么全回滚。转账类操作的底线\n",[71,16404,16405,16407,16409,16411,16413,16415,16417,16420,16422,16424],{"class":73,"line":7851},[71,16406,604],{"class":99},[71,16408,3780],{"class":99},[71,16410,4609],{"class":507},[71,16412,610],{"class":99},[71,16414,15820],{"class":507},[71,16416,626],{"class":99},[71,16418,16419],{"class":88},"begin",[71,16421,574],{"class":445},[71,16423,3736],{"class":99},[71,16425,687],{"class":445},[71,16427,16428,16430,16432,16434,16436,16439],{"class":73,"line":7871},[71,16429,15857],{"class":439},[71,16431,442],{"class":99},[71,16433,5529],{"class":88},[71,16435,475],{"class":445},[71,16437,16438],{"class":92},"\"UPDATE account SET balance = balance - 100 WHERE id = 1\"",[71,16440,1770],{"class":445},[71,16442,16443,16445,16447,16449,16451,16453,16455,16457,16459],{"class":73,"line":7876},[71,16444,1775],{"class":99},[71,16446,15875],{"class":88},[71,16448,475],{"class":445},[71,16450,3907],{"class":99},[71,16452,4324],{"class":99},[71,16454,4362],{"class":507},[71,16456,681],{"class":445},[71,16458,3736],{"class":99},[71,16460,687],{"class":445},[71,16462,16463,16465,16467,16469,16471,16474],{"class":73,"line":7911},[71,16464,15857],{"class":439},[71,16466,442],{"class":99},[71,16468,5529],{"class":88},[71,16470,475],{"class":445},[71,16472,16473],{"class":92},"\"UPDATE account SET balance = balance + 100 WHERE id = 2\"",[71,16475,1770],{"class":445},[71,16477,16478,16480,16482,16484,16486,16488,16490,16492,16494],{"class":73,"line":7936},[71,16479,1775],{"class":99},[71,16481,15875],{"class":88},[71,16483,475],{"class":445},[71,16485,3907],{"class":99},[71,16487,4324],{"class":99},[71,16489,4362],{"class":507},[71,16491,681],{"class":445},[71,16493,3736],{"class":99},[71,16495,687],{"class":445},[71,16497,16499,16502,16504,16507,16509,16511,16513,16515],{"class":73,"line":16498},58,[71,16500,16501],{"class":507},"    tx",[71,16503,626],{"class":99},[71,16505,16506],{"class":88},"commit",[71,16508,574],{"class":445},[71,16510,3736],{"class":99},[71,16512,3029],{"class":445},[71,16514,1886],{"class":77},[71,16516,16517],{"class":81}," 若中途 panic 或提前返回，tx 被 drop 自动回滚\n",[71,16519,16521,16523],{"class":73,"line":16520},59,[71,16522,851],{"class":439},[71,16524,854],{"class":445},[71,16526,16528],{"class":73,"line":16527},60,[71,16529,559],{"class":445},[14,16531,16532,16535],{},[18,16533,16534],{},"为什么事务 drop 自动回滚的设计很好？"," 你无法「忘记 rollback」——忘记 commit 就什么都不会发生，资金类系统的失败默认方向是安全的（钱不动），而不是危险的（扣了没加）。这是 Rust RAII 哲学在数据库层的体现。",[57,16537,16539],{"id":16538},"_133-编译期校验宏进阶","13.3 编译期校验宏（进阶）",[62,16541,16543],{"className":427,"code":16542,"language":429,"meta":67,"style":67},"\u002F\u002F 需要编译时能连到数据库（DATABASE_URL 环境变量或 .sqlx 离线数据）\n\u002F\u002F let user = sqlx::query_as!(User, \"SELECT id, name, email FROM user WHERE id = ?\", 1i64)\n\u002F\u002F     .fetch_one(&pool).await?;\n\u002F\u002F 表不存在、列名打错、类型不匹配 → 编译报错，不是运行时 500\n",[52,16544,16545,16552,16559,16566],{"__ignoreMap":67},[71,16546,16547,16549],{"class":73,"line":74},[71,16548,998],{"class":77},[71,16550,16551],{"class":81}," 需要编译时能连到数据库（DATABASE_URL 环境变量或 .sqlx 离线数据）\n",[71,16553,16554,16556],{"class":73,"line":85},[71,16555,998],{"class":77},[71,16557,16558],{"class":81}," let user = sqlx::query_as!(User, \"SELECT id, name, email FROM user WHERE id = ?\", 1i64)\n",[71,16560,16561,16563],{"class":73,"line":106},[71,16562,998],{"class":77},[71,16564,16565],{"class":81},"     .fetch_one(&pool).await?;\n",[71,16567,16568,16570],{"class":73,"line":121},[71,16569,998],{"class":77},[71,16571,16572],{"class":81}," 表不存在、列名打错、类型不匹配 → 编译报错，不是运行时 500\n",[11,16574,16575],{},[14,16576,16577,16578,16580,16581,16583,16584,16586],{},"与 axum 配合时，把 ",[52,16579,15676],{}," 放进 ",[52,16582,7289],{},"（见第 5 章），pool 本身是 ",[52,16585,7013],{}," 的廉价句柄。",[23,16588],{},[26,16590,16592],{"id":16591},"第-14-章-高频小工具库速查","第 14 章 高频小工具库速查",[14,16594,16595],{},"按「什么时候会想起它」组织：",[57,16597,16599],{"id":16598},"_141-环境变量配置dotenvy","14.1 环境变量配置：dotenvy",[62,16601,16603],{"className":143,"code":16602,"language":145,"meta":67,"style":67},"dotenvy = \"0.15\"\n",[52,16604,16605],{"__ignoreMap":67},[71,16606,16607],{"class":73,"line":74},[71,16608,16602],{},[62,16610,16612],{"className":427,"code":16611,"language":429,"meta":67,"style":67},"\u002F\u002F 开发时把配置写在项目根目录 .env 文件里（DATABASE_URL=mysql:\u002F\u002F...）\n\u002F\u002F main 开头一行加载，之后 std::env::var 就能读到\ndotenvy::dotenv().ok();\n\u002F\u002F 为什么用 .ok() 忽略错误？生产环境没有 .env 文件（配置走真实环境变量），不应 panic\n",[52,16613,16614,16621,16628,16646],{"__ignoreMap":67},[71,16615,16616,16618],{"class":73,"line":74},[71,16617,998],{"class":77},[71,16619,16620],{"class":81}," 开发时把配置写在项目根目录 .env 文件里（DATABASE_URL=mysql:\u002F\u002F...）\n",[71,16622,16623,16625],{"class":73,"line":85},[71,16624,998],{"class":77},[71,16626,16627],{"class":81}," main 开头一行加载，之后 std::env::var 就能读到\n",[71,16629,16630,16633,16635,16638,16640,16642,16644],{"class":73,"line":106},[71,16631,16632],{"class":439},"dotenvy",[71,16634,442],{"class":99},[71,16636,16637],{"class":88},"dotenv",[71,16639,574],{"class":445},[71,16641,626],{"class":99},[71,16643,7418],{"class":88},[71,16645,1499],{"class":445},[71,16647,16648,16650],{"class":73,"line":121},[71,16649,998],{"class":77},[71,16651,16652],{"class":81}," 为什么用 .ok() 忽略错误？生产环境没有 .env 文件（配置走真实环境变量），不应 panic\n",[57,16654,16656],{"id":16655},"_142-uuiduuid","14.2 UUID：uuid",[62,16658,16660],{"className":143,"code":16659,"language":145,"meta":67,"style":67},"uuid = { version = \"1\", features = [\"v4\", \"serde\"] }\n",[52,16661,16662],{"__ignoreMap":67},[71,16663,16664],{"class":73,"line":74},[71,16665,16659],{},[62,16667,16669],{"className":427,"code":16668,"language":429,"meta":67,"style":67},"let id = uuid::Uuid::new_v4();            \u002F\u002F 随机 UUID，做请求 ID、资源 ID\nprintln!(\"{id}\");                          \u002F\u002F 550e8400-e29b-41d4-a716-446655440000\n",[52,16670,16671,16699],{"__ignoreMap":67},[71,16672,16673,16675,16677,16679,16682,16684,16687,16689,16692,16694,16696],{"class":73,"line":74},[71,16674,1241],{"class":99},[71,16676,2453],{"class":507},[71,16678,610],{"class":99},[71,16680,16681],{"class":439}," uuid",[71,16683,442],{"class":99},[71,16685,16686],{"class":439},"Uuid",[71,16688,442],{"class":99},[71,16690,16691],{"class":88},"new_v4",[71,16693,4305],{"class":445},[71,16695,918],{"class":77},[71,16697,16698],{"class":81}," 随机 UUID，做请求 ID、资源 ID\n",[71,16700,16701,16703,16705,16707,16709,16711,16713,16715,16717,16719],{"class":73,"line":85},[71,16702,10086],{"class":88},[71,16704,475],{"class":445},[71,16706,698],{"class":92},[71,16708,446],{"class":445},[71,16710,2424],{"class":92},[71,16712,706],{"class":445},[71,16714,698],{"class":92},[71,16716,711],{"class":445},[71,16718,11117],{"class":77},[71,16720,16721],{"class":81}," 550e8400-e29b-41d4-a716-446655440000\n",[57,16723,16725],{"id":16724},"_143-全局一次性初始化标准库-lazylock-oncelock","14.3 全局一次性初始化：标准库 LazyLock \u002F OnceLock",[62,16727,16729],{"className":427,"code":16728,"language":429,"meta":67,"style":67},"use std::sync::LazyLock;\n\u002F\u002F 1.80+ 标准库内置，别再引 lazy_static \u002F once_cell 了\nstatic CONFIG: LazyLock\u003CString> = LazyLock::new(|| {\n    std::env::var(\"APP_CONFIG\").unwrap_or_else(|_| \"default\".into())\n});\n",[52,16730,16731,16747,16754,16783,16825],{"__ignoreMap":67},[71,16732,16733,16735,16737,16739,16741,16743,16745],{"class":73,"line":74},[71,16734,436],{"class":99},[71,16736,1120],{"class":439},[71,16738,442],{"class":99},[71,16740,4027],{"class":439},[71,16742,442],{"class":99},[71,16744,13704],{"class":439},[71,16746,687],{"class":445},[71,16748,16749,16751],{"class":73,"line":85},[71,16750,998],{"class":77},[71,16752,16753],{"class":81}," 1.80+ 标准库内置，别再引 lazy_static \u002F once_cell 了\n",[71,16755,16756,16758,16761,16763,16765,16767,16769,16771,16773,16775,16777,16779,16781],{"class":73,"line":106},[71,16757,3443],{"class":99},[71,16759,16760],{"class":99}," CONFIG:",[71,16762,13748],{"class":439},[71,16764,542],{"class":445},[71,16766,545],{"class":439},[71,16768,1730],{"class":445},[71,16770,610],{"class":99},[71,16772,13748],{"class":439},[71,16774,442],{"class":99},[71,16776,3876],{"class":88},[71,16778,475],{"class":445},[71,16780,1783],{"class":99},[71,16782,502],{"class":445},[71,16784,16785,16788,16790,16792,16794,16797,16799,16802,16804,16806,16808,16810,16812,16814,16816,16819,16821,16823],{"class":73,"line":121},[71,16786,16787],{"class":439},"    std",[71,16789,442],{"class":99},[71,16791,10003],{"class":439},[71,16793,442],{"class":99},[71,16795,16796],{"class":88},"var",[71,16798,475],{"class":445},[71,16800,16801],{"class":92},"\"APP_CONFIG\"",[71,16803,681],{"class":445},[71,16805,626],{"class":99},[71,16807,10735],{"class":88},[71,16809,475],{"class":445},[71,16811,7400],{"class":99},[71,16813,3584],{"class":507},[71,16815,7400],{"class":99},[71,16817,16818],{"class":92}," \"default\"",[71,16820,626],{"class":99},[71,16822,629],{"class":88},[71,16824,2500],{"class":445},[71,16826,16827],{"class":73,"line":259},[71,16828,1440],{"class":445},[57,16830,16832],{"id":16831},"_144-编码解码base64-hex","14.4 编码解码：base64 \u002F hex",[62,16834,16836],{"className":143,"code":16835,"language":145,"meta":67,"style":67},"base64 = \"0.22\"\nhex = \"0.4\"\n",[52,16837,16838,16843],{"__ignoreMap":67},[71,16839,16840],{"class":73,"line":74},[71,16841,16842],{},"base64 = \"0.22\"\n",[71,16844,16845],{"class":73,"line":85},[71,16846,16847],{},"hex = \"0.4\"\n",[62,16849,16851],{"className":427,"code":16850,"language":429,"meta":67,"style":67},"use base64::{Engine as _, engine::general_purpose::STANDARD};\nlet b64 = STANDARD.encode(\"你好\");         \u002F\u002F 字节 -> base64 字符串\nlet raw = STANDARD.decode(&b64).unwrap(); \u002F\u002F 还原\nlet h = hex::encode(b\"abc\");              \u002F\u002F \"616263\"\n",[52,16852,16853,16888,16915,16948],{"__ignoreMap":67},[71,16854,16855,16857,16860,16862,16864,16867,16869,16871,16873,16876,16878,16881,16883,16886],{"class":73,"line":74},[71,16856,436],{"class":99},[71,16858,16859],{"class":439}," base64",[71,16861,442],{"class":99},[71,16863,446],{"class":445},[71,16865,16866],{"class":439},"Engine",[71,16868,15007],{"class":99},[71,16870,3350],{"class":472},[71,16872,452],{"class":445},[71,16874,16875],{"class":439}," engine",[71,16877,442],{"class":99},[71,16879,16880],{"class":439},"general_purpose",[71,16882,442],{"class":99},[71,16884,16885],{"class":439},"STANDARD",[71,16887,458],{"class":445},[71,16889,16890,16892,16895,16897,16900,16903,16905,16908,16910,16912],{"class":73,"line":85},[71,16891,1241],{"class":99},[71,16893,16894],{"class":507}," b64",[71,16896,610],{"class":99},[71,16898,16899],{"class":99}," STANDARD.",[71,16901,16902],{"class":88},"encode",[71,16904,475],{"class":445},[71,16906,16907],{"class":92},"\"你好\"",[71,16909,711],{"class":445},[71,16911,12281],{"class":77},[71,16913,16914],{"class":81}," 字节 -> base64 字符串\n",[71,16916,16917,16919,16921,16923,16925,16928,16930,16932,16935,16937,16939,16941,16943,16945],{"class":73,"line":106},[71,16918,1241],{"class":99},[71,16920,786],{"class":507},[71,16922,610],{"class":99},[71,16924,16899],{"class":99},[71,16926,16927],{"class":88},"decode",[71,16929,475],{"class":445},[71,16931,675],{"class":99},[71,16933,16934],{"class":507},"b64",[71,16936,681],{"class":445},[71,16938,626],{"class":99},[71,16940,1496],{"class":88},[71,16942,4305],{"class":445},[71,16944,551],{"class":77},[71,16946,16947],{"class":81}," 还原\n",[71,16949,16950,16952,16954,16956,16959,16961,16963,16965,16968,16970,16972],{"class":73,"line":121},[71,16951,1241],{"class":99},[71,16953,4290],{"class":507},[71,16955,610],{"class":99},[71,16957,16958],{"class":439}," hex",[71,16960,442],{"class":99},[71,16962,16902],{"class":88},[71,16964,475],{"class":445},[71,16966,16967],{"class":92},"b\"abc\"",[71,16969,711],{"class":445},[71,16971,1927],{"class":77},[71,16973,16974],{"class":81}," \"616263\"\n",[57,16976,16978],{"id":16977},"_145-遍历目录walkdir","14.5 遍历目录：walkdir",[62,16980,16982],{"className":143,"code":16981,"language":145,"meta":67,"style":67},"walkdir = \"2\"\n",[52,16983,16984],{"__ignoreMap":67},[71,16985,16986],{"class":73,"line":74},[71,16987,16981],{},[62,16989,16991],{"className":427,"code":16990,"language":429,"meta":67,"style":67},"use walkdir::WalkDir;\n\u002F\u002F 递归遍历，自动处理符号链接和排序\nfor entry in WalkDir::new(\".\u002Fsrc\").into_iter().filter_map(|e| e.ok()) {\n    if entry.path().extension().is_some_and(|e| e == \"rs\") {\n        println!(\"{}\", entry.path().display());\n    }\n}\n",[52,16992,16993,17007,17014,17067,17110,17139,17143],{"__ignoreMap":67},[71,16994,16995,16997,17000,17002,17005],{"class":73,"line":74},[71,16996,436],{"class":99},[71,16998,16999],{"class":439}," walkdir",[71,17001,442],{"class":99},[71,17003,17004],{"class":439},"WalkDir",[71,17006,687],{"class":445},[71,17008,17009,17011],{"class":73,"line":85},[71,17010,998],{"class":77},[71,17012,17013],{"class":81}," 递归遍历，自动处理符号链接和排序\n",[71,17015,17016,17019,17022,17024,17027,17029,17031,17033,17036,17038,17040,17042,17044,17046,17049,17051,17053,17055,17057,17059,17061,17063,17065],{"class":73,"line":106},[71,17017,17018],{"class":99},"for",[71,17020,17021],{"class":507}," entry",[71,17023,4173],{"class":99},[71,17025,17026],{"class":439}," WalkDir",[71,17028,442],{"class":99},[71,17030,3876],{"class":88},[71,17032,475],{"class":445},[71,17034,17035],{"class":92},"\".\u002Fsrc\"",[71,17037,681],{"class":445},[71,17039,626],{"class":99},[71,17041,14193],{"class":88},[71,17043,574],{"class":445},[71,17045,626],{"class":99},[71,17047,17048],{"class":88},"filter_map",[71,17050,475],{"class":445},[71,17052,7400],{"class":99},[71,17054,2047],{"class":507},[71,17056,7400],{"class":99},[71,17058,2090],{"class":507},[71,17060,626],{"class":99},[71,17062,7418],{"class":88},[71,17064,3044],{"class":445},[71,17066,502],{"class":445},[71,17068,17069,17071,17073,17075,17077,17079,17081,17084,17086,17088,17091,17093,17095,17097,17099,17101,17103,17106,17108],{"class":73,"line":121},[71,17070,1894],{"class":99},[71,17072,17021],{"class":507},[71,17074,626],{"class":99},[71,17076,1709],{"class":88},[71,17078,574],{"class":445},[71,17080,626],{"class":99},[71,17082,17083],{"class":88},"extension",[71,17085,574],{"class":445},[71,17087,626],{"class":99},[71,17089,17090],{"class":88},"is_some_and",[71,17092,475],{"class":445},[71,17094,7400],{"class":99},[71,17096,2047],{"class":507},[71,17098,7400],{"class":99},[71,17100,2090],{"class":507},[71,17102,2456],{"class":99},[71,17104,17105],{"class":92}," \"rs\"",[71,17107,681],{"class":445},[71,17109,502],{"class":445},[71,17111,17112,17114,17116,17118,17120,17122,17124,17126,17128,17130,17132,17134,17137],{"class":73,"line":259},[71,17113,4521],{"class":88},[71,17115,475],{"class":445},[71,17117,698],{"class":92},[71,17119,741],{"class":445},[71,17121,698],{"class":92},[71,17123,452],{"class":445},[71,17125,17021],{"class":507},[71,17127,626],{"class":99},[71,17129,1709],{"class":88},[71,17131,574],{"class":445},[71,17133,626],{"class":99},[71,17135,17136],{"class":88},"display",[71,17138,2019],{"class":445},[71,17140,17141],{"class":73,"line":273},[71,17142,1935],{"class":445},[71,17144,17145],{"class":73,"line":286},[71,17146,559],{"class":445},[57,17148,17150],{"id":17149},"_146-临时文件tempfile测试常用","14.6 临时文件：tempfile（测试常用）",[62,17152,17154],{"className":143,"code":17153,"language":145,"meta":67,"style":67},"tempfile = \"3\"\n",[52,17155,17156],{"__ignoreMap":67},[71,17157,17158],{"class":73,"line":74},[71,17159,17153],{},[62,17161,17163],{"className":427,"code":17162,"language":429,"meta":67,"style":67},"let mut f = tempfile::NamedTempFile::new().unwrap();\nuse std::io::Write;\nwriteln!(f, \"test\").unwrap();\n\u002F\u002F 为什么用库而不是自己拼 \u002Ftmp\u002Fxxx？自动防重名、权限正确、离开作用域自动删除，测试不残留垃圾\n",[52,17164,17165,17195,17212,17234],{"__ignoreMap":67},[71,17166,17167,17169,17171,17173,17175,17178,17180,17183,17185,17187,17189,17191,17193],{"class":73,"line":74},[71,17168,1241],{"class":99},[71,17170,3780],{"class":99},[71,17172,3783],{"class":507},[71,17174,610],{"class":99},[71,17176,17177],{"class":439}," tempfile",[71,17179,442],{"class":99},[71,17181,17182],{"class":439},"NamedTempFile",[71,17184,442],{"class":99},[71,17186,3876],{"class":88},[71,17188,574],{"class":445},[71,17190,626],{"class":99},[71,17192,1496],{"class":88},[71,17194,1499],{"class":445},[71,17196,17197,17199,17201,17203,17205,17207,17210],{"class":73,"line":85},[71,17198,436],{"class":99},[71,17200,1120],{"class":439},[71,17202,442],{"class":99},[71,17204,3648],{"class":439},[71,17206,442],{"class":99},[71,17208,17209],{"class":439},"Write",[71,17211,687],{"class":445},[71,17213,17214,17217,17219,17221,17223,17226,17228,17230,17232],{"class":73,"line":106},[71,17215,17216],{"class":88},"writeln!",[71,17218,475],{"class":445},[71,17220,12678],{"class":507},[71,17222,452],{"class":445},[71,17224,17225],{"class":92}," \"test\"",[71,17227,681],{"class":445},[71,17229,626],{"class":99},[71,17231,1496],{"class":88},[71,17233,1499],{"class":445},[71,17235,17236,17238],{"class":73,"line":121},[71,17237,998],{"class":77},[71,17239,17240],{"class":81}," 为什么用库而不是自己拼 \u002Ftmp\u002Fxxx？自动防重名、权限正确、离开作用域自动删除，测试不残留垃圾\n",[57,17242,17244],{"id":17243},"_147-csv-读写csv","14.7 CSV 读写：csv",[62,17246,17248],{"className":143,"code":17247,"language":145,"meta":67,"style":67},"csv = \"1\"\n",[52,17249,17250],{"__ignoreMap":67},[71,17251,17252],{"class":73,"line":74},[71,17253,17247],{},[62,17255,17257],{"className":427,"code":17256,"language":429,"meta":67,"style":67},"\u002F\u002F 读取\nlet mut rdr = csv::Reader::from_path(\"data.csv\").unwrap();\nfor row in rdr.records() {\n    let row = row.unwrap();\n    println!(\"{:?}\", row);\n}\n\u002F\u002F 配合 serde 直接反序列化为结构体\n\u002F\u002F for row in rdr.deserialize::\u003CMyRow>() { let r: MyRow = row.unwrap(); }\n\n\u002F\u002F 写入\nlet mut wtr = csv::Writer::from_path(\"out.csv\").unwrap();\nwtr.write_record([\"name\", \"age\"]).unwrap();\nwtr.write_record([\"小明\", \"20\"]).unwrap();\nwtr.flush().unwrap();\n",[52,17258,17259,17266,17303,17323,17339,17361,17365,17372,17379,17383,17390,17425,17453,17478],{"__ignoreMap":67},[71,17260,17261,17263],{"class":73,"line":74},[71,17262,998],{"class":77},[71,17264,17265],{"class":81}," 读取\n",[71,17267,17268,17270,17272,17275,17277,17280,17282,17285,17287,17290,17292,17295,17297,17299,17301],{"class":73,"line":85},[71,17269,1241],{"class":99},[71,17271,3780],{"class":99},[71,17273,17274],{"class":507}," rdr",[71,17276,610],{"class":99},[71,17278,17279],{"class":439}," csv",[71,17281,442],{"class":99},[71,17283,17284],{"class":439},"Reader",[71,17286,442],{"class":99},[71,17288,17289],{"class":88},"from_path",[71,17291,475],{"class":445},[71,17293,17294],{"class":92},"\"data.csv\"",[71,17296,681],{"class":445},[71,17298,626],{"class":99},[71,17300,1496],{"class":88},[71,17302,1499],{"class":445},[71,17304,17305,17307,17310,17312,17314,17316,17319,17321],{"class":73,"line":106},[71,17306,17018],{"class":99},[71,17308,17309],{"class":507}," row",[71,17311,4173],{"class":99},[71,17313,17274],{"class":507},[71,17315,626],{"class":99},[71,17317,17318],{"class":88},"records",[71,17320,574],{"class":445},[71,17322,502],{"class":445},[71,17324,17325,17327,17329,17331,17333,17335,17337],{"class":73,"line":121},[71,17326,604],{"class":99},[71,17328,17309],{"class":507},[71,17330,610],{"class":99},[71,17332,17309],{"class":507},[71,17334,626],{"class":99},[71,17336,1496],{"class":88},[71,17338,1499],{"class":445},[71,17340,17341,17343,17345,17347,17349,17351,17353,17355,17357,17359],{"class":73,"line":259},[71,17342,693],{"class":88},[71,17344,475],{"class":445},[71,17346,698],{"class":92},[71,17348,446],{"class":445},[71,17350,2107],{"class":92},[71,17352,706],{"class":445},[71,17354,698],{"class":92},[71,17356,452],{"class":445},[71,17358,17309],{"class":507},[71,17360,765],{"class":445},[71,17362,17363],{"class":73,"line":273},[71,17364,559],{"class":445},[71,17366,17367,17369],{"class":73,"line":286},[71,17368,998],{"class":77},[71,17370,17371],{"class":81}," 配合 serde 直接反序列化为结构体\n",[71,17373,17374,17376],{"class":73,"line":300},[71,17375,998],{"class":77},[71,17377,17378],{"class":81}," for row in rdr.deserialize::\u003CMyRow>() { let r: MyRow = row.unwrap(); }\n",[71,17380,17381],{"class":73,"line":316},[71,17382,464],{"emptyLinePlaceholder":463},[71,17384,17385,17387],{"class":73,"line":330},[71,17386,998],{"class":77},[71,17388,17389],{"class":81}," 写入\n",[71,17391,17392,17394,17396,17399,17401,17403,17405,17408,17410,17412,17414,17417,17419,17421,17423],{"class":73,"line":352},[71,17393,1241],{"class":99},[71,17395,3780],{"class":99},[71,17397,17398],{"class":507}," wtr",[71,17400,610],{"class":99},[71,17402,17279],{"class":439},[71,17404,442],{"class":99},[71,17406,17407],{"class":439},"Writer",[71,17409,442],{"class":99},[71,17411,17289],{"class":88},[71,17413,475],{"class":445},[71,17415,17416],{"class":92},"\"out.csv\"",[71,17418,681],{"class":445},[71,17420,626],{"class":99},[71,17422,1496],{"class":88},[71,17424,1499],{"class":445},[71,17426,17427,17430,17432,17435,17437,17439,17441,17444,17447,17449,17451],{"class":73,"line":601},[71,17428,17429],{"class":507},"wtr",[71,17431,626],{"class":99},[71,17433,17434],{"class":88},"write_record",[71,17436,14654],{"class":445},[71,17438,1334],{"class":92},[71,17440,452],{"class":445},[71,17442,17443],{"class":92}," \"age\"",[71,17445,17446],{"class":445},"])",[71,17448,626],{"class":99},[71,17450,1496],{"class":88},[71,17452,1499],{"class":445},[71,17454,17455,17457,17459,17461,17463,17465,17467,17470,17472,17474,17476],{"class":73,"line":656},[71,17456,17429],{"class":507},[71,17458,626],{"class":99},[71,17460,17434],{"class":88},[71,17462,14654],{"class":445},[71,17464,2493],{"class":92},[71,17466,452],{"class":445},[71,17468,17469],{"class":92}," \"20\"",[71,17471,17446],{"class":445},[71,17473,626],{"class":99},[71,17475,1496],{"class":88},[71,17477,1499],{"class":445},[71,17479,17480,17482,17484,17486,17488,17490,17492],{"class":73,"line":690},[71,17481,17429],{"class":507},[71,17483,626],{"class":99},[71,17485,3848],{"class":88},[71,17487,574],{"class":445},[71,17489,626],{"class":99},[71,17491,1496],{"class":88},[71,17493,1499],{"class":445},[57,17495,17497],{"id":17496},"_148-进度条indicatifcli-工具体验拉满","14.8 进度条：indicatif（CLI 工具体验拉满）",[62,17499,17501],{"className":143,"code":17500,"language":145,"meta":67,"style":67},"indicatif = \"0.17\"\n",[52,17502,17503],{"__ignoreMap":67},[71,17504,17505],{"class":73,"line":74},[71,17506,17500],{},[62,17508,17510],{"className":427,"code":17509,"language":429,"meta":67,"style":67},"use indicatif::{ProgressBar, ProgressStyle};\nlet pb = ProgressBar::new(1000);\npb.set_style(ProgressStyle::default_bar()\n    .template(\"{bar:40.cyan\u002Fblue} {pos}\u002F{len} ETA:{eta}\").unwrap());\nfor _ in 0..1000 {\n    std::thread::sleep(std::time::Duration::from_millis(2));\n    pb.inc(1);\n}\npb.finish_with_message(\"完成\");\n",[52,17511,17512,17533,17556,17578,17632,17648,17683,17699,17703],{"__ignoreMap":67},[71,17513,17514,17516,17519,17521,17523,17526,17528,17531],{"class":73,"line":74},[71,17515,436],{"class":99},[71,17517,17518],{"class":439}," indicatif",[71,17520,442],{"class":99},[71,17522,446],{"class":445},[71,17524,17525],{"class":439},"ProgressBar",[71,17527,452],{"class":445},[71,17529,17530],{"class":439}," ProgressStyle",[71,17532,458],{"class":445},[71,17534,17535,17537,17540,17542,17545,17547,17549,17551,17554],{"class":73,"line":85},[71,17536,1241],{"class":99},[71,17538,17539],{"class":507}," pb",[71,17541,610],{"class":99},[71,17543,17544],{"class":439}," ProgressBar",[71,17546,442],{"class":99},[71,17548,3876],{"class":88},[71,17550,475],{"class":445},[71,17552,17553],{"class":88},"1000",[71,17555,765],{"class":445},[71,17557,17558,17561,17563,17566,17568,17571,17573,17576],{"class":73,"line":106},[71,17559,17560],{"class":507},"pb",[71,17562,626],{"class":99},[71,17564,17565],{"class":88},"set_style",[71,17567,475],{"class":445},[71,17569,17570],{"class":439},"ProgressStyle",[71,17572,442],{"class":99},[71,17574,17575],{"class":88},"default_bar",[71,17577,3983],{"class":445},[71,17579,17580,17583,17586,17588,17590,17592,17595,17597,17599,17602,17604,17606,17608,17610,17612,17615,17617,17620,17622,17624,17626,17628,17630],{"class":73,"line":121},[71,17581,17582],{"class":99},"    .",[71,17584,17585],{"class":88},"template",[71,17587,475],{"class":445},[71,17589,698],{"class":92},[71,17591,446],{"class":445},[71,17593,17594],{"class":92},"bar:40.cyan\u002Fblue",[71,17596,706],{"class":445},[71,17598,615],{"class":445},[71,17600,17601],{"class":92},"pos",[71,17603,706],{"class":445},[71,17605,15458],{"class":92},[71,17607,446],{"class":445},[71,17609,2016],{"class":92},[71,17611,706],{"class":445},[71,17613,17614],{"class":92}," ETA:",[71,17616,446],{"class":445},[71,17618,17619],{"class":92},"eta",[71,17621,706],{"class":445},[71,17623,698],{"class":92},[71,17625,681],{"class":445},[71,17627,626],{"class":99},[71,17629,1496],{"class":88},[71,17631,2019],{"class":445},[71,17633,17634,17636,17638,17640,17642,17644,17646],{"class":73,"line":259},[71,17635,17018],{"class":99},[71,17637,3350],{"class":507},[71,17639,4173],{"class":99},[71,17641,1868],{"class":88},[71,17643,4178],{"class":99},[71,17645,17553],{"class":88},[71,17647,502],{"class":445},[71,17649,17650,17652,17654,17657,17659,17661,17663,17665,17667,17669,17671,17673,17675,17677,17679,17681],{"class":73,"line":273},[71,17651,16787],{"class":439},[71,17653,442],{"class":99},[71,17655,17656],{"class":439},"thread",[71,17658,442],{"class":99},[71,17660,2850],{"class":88},[71,17662,475],{"class":445},[71,17664,2855],{"class":439},[71,17666,442],{"class":99},[71,17668,2845],{"class":439},[71,17670,442],{"class":99},[71,17672,2864],{"class":439},[71,17674,442],{"class":99},[71,17676,3017],{"class":88},[71,17678,475],{"class":445},[71,17680,13233],{"class":88},[71,17682,4146],{"class":445},[71,17684,17685,17688,17690,17693,17695,17697],{"class":73,"line":286},[71,17686,17687],{"class":507},"    pb",[71,17689,626],{"class":99},[71,17691,17692],{"class":88},"inc",[71,17694,475],{"class":445},[71,17696,2061],{"class":88},[71,17698,765],{"class":445},[71,17700,17701],{"class":73,"line":300},[71,17702,559],{"class":445},[71,17704,17705,17707,17709,17712,17714,17717],{"class":73,"line":316},[71,17706,17560],{"class":507},[71,17708,626],{"class":99},[71,17710,17711],{"class":88},"finish_with_message",[71,17713,475],{"class":445},[71,17715,17716],{"class":92},"\"完成\"",[71,17718,765],{"class":445},[57,17720,17722],{"id":17721},"_149-url-解析url","14.9 URL 解析：url",[62,17724,17726],{"className":143,"code":17725,"language":145,"meta":67,"style":67},"url = \"2\"\n",[52,17727,17728],{"__ignoreMap":67},[71,17729,17730],{"class":73,"line":74},[71,17731,17725],{},[62,17733,17735],{"className":427,"code":17734,"language":429,"meta":67,"style":67},"let u = url::Url::parse(\"https:\u002F\u002Fexample.com:8080\u002Fpath?a=1#frag\").unwrap();\nprintln!(\"{} {} {}\", u.host_str().unwrap(), u.port().unwrap_or(443), u.path());\n\u002F\u002F 为什么不用字符串切分？URL 有百分号编码、默认端口、相对路径解析等一堆边角，\n\u002F\u002F 手写必错，用标准库式的解析器一次到位\n",[52,17736,17737,17769,17830,17837],{"__ignoreMap":67},[71,17738,17739,17741,17743,17745,17747,17749,17752,17754,17756,17758,17761,17763,17765,17767],{"class":73,"line":74},[71,17740,1241],{"class":99},[71,17742,607],{"class":507},[71,17744,610],{"class":99},[71,17746,5753],{"class":439},[71,17748,442],{"class":99},[71,17750,17751],{"class":439},"Url",[71,17753,442],{"class":99},[71,17755,9684],{"class":88},[71,17757,475],{"class":445},[71,17759,17760],{"class":92},"\"https:\u002F\u002Fexample.com:8080\u002Fpath?a=1#frag\"",[71,17762,681],{"class":445},[71,17764,626],{"class":99},[71,17766,1496],{"class":88},[71,17768,1499],{"class":445},[71,17770,17771,17773,17775,17777,17779,17781,17783,17785,17787,17789,17791,17794,17796,17798,17800,17802,17804,17806,17809,17811,17813,17815,17817,17820,17822,17824,17826,17828],{"class":73,"line":85},[71,17772,10086],{"class":88},[71,17774,475],{"class":445},[71,17776,698],{"class":92},[71,17778,741],{"class":445},[71,17780,15414],{"class":445},[71,17782,15414],{"class":445},[71,17784,698],{"class":92},[71,17786,452],{"class":445},[71,17788,607],{"class":507},[71,17790,626],{"class":99},[71,17792,17793],{"class":88},"host_str",[71,17795,574],{"class":445},[71,17797,626],{"class":99},[71,17799,1496],{"class":88},[71,17801,632],{"class":445},[71,17803,607],{"class":507},[71,17805,626],{"class":99},[71,17807,17808],{"class":88},"port",[71,17810,574],{"class":445},[71,17812,626],{"class":99},[71,17814,7243],{"class":88},[71,17816,475],{"class":445},[71,17818,17819],{"class":88},"443",[71,17821,2398],{"class":445},[71,17823,607],{"class":507},[71,17825,626],{"class":99},[71,17827,1709],{"class":88},[71,17829,2019],{"class":445},[71,17831,17832,17834],{"class":73,"line":106},[71,17833,998],{"class":77},[71,17835,17836],{"class":81}," 为什么不用字符串切分？URL 有百分号编码、默认端口、相对路径解析等一堆边角，\n",[71,17838,17839,17841],{"class":73,"line":121},[71,17840,998],{"class":77},[71,17842,17843],{"class":81}," 手写必错，用标准库式的解析器一次到位\n",[57,17845,17847],{"id":17846},"_1410-优雅退出ctrlc-tokiosignal","14.10 优雅退出：ctrlc \u002F tokio::signal",[62,17849,17851],{"className":427,"code":17850,"language":429,"meta":67,"style":67},"\u002F\u002F tokio 程序里监听 Ctrl+C，先收尾再退出（关闭连接池、刷日志、保存状态）\ntokio::select! {\n    _ = run_server() => {},\n    _ = tokio::signal::ctrl_c() => { println!(\"收到退出信号，开始收尾\"); }\n}\n\u002F\u002F 为什么不能直接被杀？直接 SIGKILL 可能丢未落库数据、连接不释放。\n\u002F\u002F 优雅停机（graceful shutdown）是服务端工程素养的基本功。\n",[52,17852,17853,17860,17870,17887,17922,17926,17933],{"__ignoreMap":67},[71,17854,17855,17857],{"class":73,"line":74},[71,17856,998],{"class":77},[71,17858,17859],{"class":81}," tokio 程序里监听 Ctrl+C，先收尾再退出（关闭连接池、刷日志、保存状态）\n",[71,17861,17862,17864,17866,17868],{"class":73,"line":85},[71,17863,2777],{"class":439},[71,17865,442],{"class":99},[71,17867,3273],{"class":88},[71,17869,502],{"class":445},[71,17871,17872,17875,17877,17880,17882,17884],{"class":73,"line":106},[71,17873,17874],{"class":507},"    _",[71,17876,610],{"class":99},[71,17878,17879],{"class":88}," run_server",[71,17881,574],{"class":445},[71,17883,2563],{"class":99},[71,17885,17886],{"class":445}," {},\n",[71,17888,17889,17891,17893,17895,17897,17900,17902,17905,17907,17909,17911,17913,17915,17918,17920],{"class":73,"line":121},[71,17890,17874],{"class":507},[71,17892,610],{"class":99},[71,17894,113],{"class":439},[71,17896,442],{"class":99},[71,17898,17899],{"class":439},"signal",[71,17901,442],{"class":99},[71,17903,17904],{"class":88},"ctrl_c",[71,17906,574],{"class":445},[71,17908,2563],{"class":99},[71,17910,615],{"class":445},[71,17912,2566],{"class":88},[71,17914,475],{"class":445},[71,17916,17917],{"class":92},"\"收到退出信号，开始收尾\"",[71,17919,711],{"class":445},[71,17921,1180],{"class":445},[71,17923,17924],{"class":73,"line":259},[71,17925,559],{"class":445},[71,17927,17928,17930],{"class":73,"line":273},[71,17929,998],{"class":77},[71,17931,17932],{"class":81}," 为什么不能直接被杀？直接 SIGKILL 可能丢未落库数据、连接不释放。\n",[71,17934,17935,17937],{"class":73,"line":286},[71,17936,998],{"class":77},[71,17938,17939],{"class":81}," 优雅停机（graceful shutdown）是服务端工程素养的基本功。\n",[23,17941],{},[26,17943,17945],{"id":17944},"第-15-章-测试相关的常用工具","第 15 章 测试相关的常用工具",[57,17947,17949],{"id":17948},"_151-内置测试框架回顾","15.1 内置测试框架回顾",[62,17951,17953],{"className":427,"code":17952,"language":429,"meta":67,"style":67},"fn add(a: i32, b: i32) -> i32 { a + b }\n\n#[cfg(test)]            \u002F\u002F 只在 cargo test 时编译，不进正式产物\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_add() {\n        assert_eq!(add(1, 2), 3);            \u002F\u002F 相等断言，失败时打印两边值\n        assert_ne!(add(1, 2), 4);\n        assert!(add(2, 2) > 3, \"自定义失败信息\"); \u002F\u002F 第二参数是失败时的提示\n    }\n\n    #[test]\n    #[should_panic(expected = \"除以零\")]      \u002F\u002F 断言「必须 panic 且信息包含指定文本」\n    fn test_div_zero() { panic!(\"除以零\"); }\n\n    #[test]\n    fn test_result() -> Result\u003C(), String> { \u002F\u002F 返回 Result 的测试：Err 即失败，能用 ?\n        if add(2, 2) == 4 { Ok(()) } else { Err(\"算错了\".into()) }\n    }\n\n    #[test]\n    #[ignore = \"需要真实数据库，CI 里单独跑\"] \u002F\u002F cargo test -- --ignored 才运行\n    fn test_integration() {}\n}\n",[52,17954,17955,17993,17997,18016,18025,18036,18040,18048,18059,18088,18111,18146,18150,18154,18162,18186,18209,18213,18221,18247,18296,18300,18304,18312,18331,18342],{"__ignoreMap":67},[71,17956,17957,17959,17961,17963,17965,17967,17969,17971,17973,17975,17977,17979,17981,17983,17985,17987,17989,17991],{"class":73,"line":74},[71,17958,568],{"class":99},[71,17960,93],{"class":88},[71,17962,475],{"class":445},[71,17964,38],{"class":507},[71,17966,511],{"class":99},[71,17968,1843],{"class":439},[71,17970,452],{"class":445},[71,17972,12200],{"class":507},[71,17974,511],{"class":99},[71,17976,1843],{"class":439},[71,17978,681],{"class":445},[71,17980,577],{"class":99},[71,17982,1843],{"class":439},[71,17984,615],{"class":445},[71,17986,14455],{"class":507},[71,17988,7362],{"class":99},[71,17990,12200],{"class":507},[71,17992,1180],{"class":445},[71,17994,17995],{"class":73,"line":85},[71,17996,464],{"emptyLinePlaceholder":463},[71,17998,17999,18001,18004,18006,18009,18011,18013],{"class":73,"line":106},[71,18000,469],{"class":445},[71,18002,18003],{"class":472},"cfg",[71,18005,475],{"class":445},[71,18007,18008],{"class":472},"test",[71,18010,915],{"class":445},[71,18012,918],{"class":77},[71,18014,18015],{"class":81}," 只在 cargo test 时编译，不进正式产物\n",[71,18017,18018,18020,18023],{"class":73,"line":121},[71,18019,11704],{"class":99},[71,18021,18022],{"class":88}," tests",[71,18024,502],{"class":445},[71,18026,18027,18029,18032,18034],{"class":73,"line":259},[71,18028,11714],{"class":99},[71,18030,18031],{"class":507}," super",[71,18033,12113],{"class":99},[71,18035,687],{"class":445},[71,18037,18038],{"class":73,"line":273},[71,18039,464],{"emptyLinePlaceholder":463},[71,18041,18042,18044,18046],{"class":73,"line":286},[71,18043,898],{"class":445},[71,18045,18008],{"class":472},[71,18047,2785],{"class":445},[71,18049,18050,18052,18055,18057],{"class":73,"line":300},[71,18051,8101],{"class":99},[71,18053,18054],{"class":88}," test_add",[71,18056,574],{"class":445},[71,18058,502],{"class":445},[71,18060,18061,18064,18066,18069,18071,18073,18075,18077,18079,18081,18083,18085],{"class":73,"line":316},[71,18062,18063],{"class":88},"        assert_eq!",[71,18065,475],{"class":445},[71,18067,18068],{"class":88},"add",[71,18070,475],{"class":445},[71,18072,2061],{"class":88},[71,18074,452],{"class":445},[71,18076,11217],{"class":88},[71,18078,2398],{"class":445},[71,18080,1421],{"class":88},[71,18082,711],{"class":445},[71,18084,918],{"class":77},[71,18086,18087],{"class":81}," 相等断言，失败时打印两边值\n",[71,18089,18090,18093,18095,18097,18099,18101,18103,18105,18107,18109],{"class":73,"line":330},[71,18091,18092],{"class":88},"        assert_ne!",[71,18094,475],{"class":445},[71,18096,18068],{"class":88},[71,18098,475],{"class":445},[71,18100,2061],{"class":88},[71,18102,452],{"class":445},[71,18104,11217],{"class":88},[71,18106,2398],{"class":445},[71,18108,9360],{"class":88},[71,18110,765],{"class":445},[71,18112,18113,18116,18118,18120,18122,18124,18126,18128,18130,18132,18134,18136,18139,18141,18143],{"class":73,"line":352},[71,18114,18115],{"class":88},"        assert!",[71,18117,475],{"class":445},[71,18119,18068],{"class":88},[71,18121,475],{"class":445},[71,18123,13233],{"class":88},[71,18125,452],{"class":445},[71,18127,11217],{"class":88},[71,18129,681],{"class":445},[71,18131,1899],{"class":445},[71,18133,1421],{"class":88},[71,18135,452],{"class":445},[71,18137,18138],{"class":92}," \"自定义失败信息\"",[71,18140,711],{"class":445},[71,18142,551],{"class":77},[71,18144,18145],{"class":81}," 第二参数是失败时的提示\n",[71,18147,18148],{"class":73,"line":601},[71,18149,1935],{"class":445},[71,18151,18152],{"class":73,"line":656},[71,18153,464],{"emptyLinePlaceholder":463},[71,18155,18156,18158,18160],{"class":73,"line":690},[71,18157,898],{"class":445},[71,18159,18008],{"class":472},[71,18161,2785],{"class":445},[71,18163,18164,18166,18169,18171,18174,18176,18179,18181,18183],{"class":73,"line":719},[71,18165,898],{"class":445},[71,18167,18168],{"class":472},"should_panic",[71,18170,475],{"class":445},[71,18172,18173],{"class":472},"expected ",[71,18175,909],{"class":99},[71,18177,18178],{"class":92}," \"除以零\"",[71,18180,915],{"class":445},[71,18182,9015],{"class":77},[71,18184,18185],{"class":81}," 断言「必须 panic 且信息包含指定文本」\n",[71,18187,18188,18190,18193,18195,18197,18200,18202,18205,18207],{"class":73,"line":724},[71,18189,8101],{"class":99},[71,18191,18192],{"class":88}," test_div_zero",[71,18194,574],{"class":445},[71,18196,615],{"class":445},[71,18198,18199],{"class":88}," panic!",[71,18201,475],{"class":445},[71,18203,18204],{"class":92},"\"除以零\"",[71,18206,711],{"class":445},[71,18208,1180],{"class":445},[71,18210,18211],{"class":73,"line":732},[71,18212,464],{"emptyLinePlaceholder":463},[71,18214,18215,18217,18219],{"class":73,"line":768},[71,18216,898],{"class":445},[71,18218,18008],{"class":472},[71,18220,2785],{"class":445},[71,18222,18223,18225,18228,18230,18232,18234,18236,18238,18240,18242,18244],{"class":73,"line":773},[71,18224,8101],{"class":99},[71,18226,18227],{"class":88}," test_result",[71,18229,574],{"class":445},[71,18231,577],{"class":99},[71,18233,1661],{"class":439},[71,18235,15789],{"class":445},[71,18237,514],{"class":439},[71,18239,1730],{"class":445},[71,18241,615],{"class":445},[71,18243,551],{"class":77},[71,18245,18246],{"class":81}," 返回 Result 的测试：Err 即失败，能用 ?\n",[71,18248,18249,18252,18254,18256,18258,18260,18262,18264,18266,18268,18270,18272,18275,18277,18279,18281,18283,18285,18288,18290,18292,18294],{"class":73,"line":781},[71,18250,18251],{"class":99},"        if",[71,18253,93],{"class":88},[71,18255,475],{"class":445},[71,18257,13233],{"class":88},[71,18259,452],{"class":445},[71,18261,11217],{"class":88},[71,18263,681],{"class":445},[71,18265,2456],{"class":99},[71,18267,9360],{"class":88},[71,18269,615],{"class":445},[71,18271,3032],{"class":439},[71,18273,18274],{"class":445},"(())",[71,18276,6465],{"class":445},[71,18278,7588],{"class":99},[71,18280,615],{"class":445},[71,18282,2042],{"class":439},[71,18284,475],{"class":445},[71,18286,18287],{"class":92},"\"算错了\"",[71,18289,626],{"class":99},[71,18291,629],{"class":88},[71,18293,3044],{"class":445},[71,18295,1180],{"class":445},[71,18297,18298],{"class":73,"line":796},[71,18299,1935],{"class":445},[71,18301,18302],{"class":73,"line":828},[71,18303,464],{"emptyLinePlaceholder":463},[71,18305,18306,18308,18310],{"class":73,"line":848},[71,18307,898],{"class":445},[71,18309,18008],{"class":472},[71,18311,2785],{"class":445},[71,18313,18314,18316,18319,18321,18324,18326,18328],{"class":73,"line":857},[71,18315,898],{"class":445},[71,18317,18318],{"class":472},"ignore ",[71,18320,909],{"class":99},[71,18322,18323],{"class":92}," \"需要真实数据库，CI 里单独跑\"",[71,18325,1337],{"class":445},[71,18327,551],{"class":77},[71,18329,18330],{"class":81}," cargo test -- --ignored 才运行\n",[71,18332,18333,18335,18338,18340],{"class":73,"line":2022},[71,18334,8101],{"class":99},[71,18336,18337],{"class":88}," test_integration",[71,18339,574],{"class":445},[71,18341,2629],{"class":445},[71,18343,18344],{"class":73,"line":2027},[71,18345,559],{"class":445},[57,18347,18349],{"id":18348},"_152-浮点数比较approx","15.2 浮点数比较：approx",[62,18351,18353],{"className":143,"code":18352,"language":145,"meta":67,"style":67},"[dev-dependencies]       # 只在测试时编译的依赖放这里\napprox = \"0.5\"\n",[52,18354,18355,18360],{"__ignoreMap":67},[71,18356,18357],{"class":73,"line":74},[71,18358,18359],{},"[dev-dependencies]       # 只在测试时编译的依赖放这里\n",[71,18361,18362],{"class":73,"line":85},[71,18363,18364],{},"approx = \"0.5\"\n",[62,18366,18368],{"className":427,"code":18367,"language":429,"meta":67,"style":67},"\u002F\u002F 为什么不直接 assert_eq!(0.1 + 0.2, 0.3)？浮点精度问题，0.1+0.2 ≠ 0.3 是成立的！\nassert_relative_eq!(0.1 + 0.2, 0.3, epsilon = 1e-10); \u002F\u002F 需要 use approx::assert_relative_eq;\n",[52,18369,18370,18377],{"__ignoreMap":67},[71,18371,18372,18374],{"class":73,"line":74},[71,18373,998],{"class":77},[71,18375,18376],{"class":81}," 为什么不直接 assert_eq!(0.1 + 0.2, 0.3)？浮点精度问题，0.1+0.2 ≠ 0.3 是成立的！\n",[71,18378,18379,18382,18384,18386,18388,18390,18392,18394,18396,18398,18400,18402,18404,18406,18408,18411,18413,18415,18418,18420,18422,18424],{"class":73,"line":85},[71,18380,18381],{"class":88},"assert_relative_eq!",[71,18383,475],{"class":445},[71,18385,1329],{"class":88},[71,18387,626],{"class":445},[71,18389,2061],{"class":88},[71,18391,7362],{"class":99},[71,18393,1868],{"class":88},[71,18395,626],{"class":445},[71,18397,13233],{"class":88},[71,18399,452],{"class":445},[71,18401,1868],{"class":88},[71,18403,626],{"class":445},[71,18405,4432],{"class":88},[71,18407,452],{"class":445},[71,18409,18410],{"class":507}," epsilon",[71,18412,610],{"class":99},[71,18414,4272],{"class":88},[71,18416,18417],{"class":99},"e-",[71,18419,3326],{"class":88},[71,18421,711],{"class":445},[71,18423,551],{"class":77},[71,18425,18426],{"class":81}," 需要 use approx::assert_relative_eq;\n",[57,18428,18430],{"id":18429},"_153-异步测试直接用-tokio-宏","15.3 异步测试：直接用 tokio 宏",[62,18432,18434],{"className":427,"code":18433,"language":429,"meta":67,"style":67},"#[tokio::test]           \u002F\u002F 自动给你建运行时\nasync fn test_async() {\n    let resp = async { 42 }.await;\n    assert_eq!(resp, 42);\n}\n",[52,18435,18436,18454,18467,18488,18503],{"__ignoreMap":67},[71,18437,18438,18440,18442,18444,18446,18448,18451],{"class":73,"line":74},[71,18439,469],{"class":445},[71,18441,2777],{"class":472},[71,18443,442],{"class":99},[71,18445,18008],{"class":472},[71,18447,1337],{"class":445},[71,18449,18450],{"class":77},"           \u002F\u002F",[71,18452,18453],{"class":81}," 自动给你建运行时\n",[71,18455,18456,18458,18460,18463,18465],{"class":73,"line":85},[71,18457,2790],{"class":99},[71,18459,2793],{"class":99},[71,18461,18462],{"class":88}," test_async",[71,18464,574],{"class":445},[71,18466,502],{"class":445},[71,18468,18469,18471,18473,18475,18477,18479,18482,18484,18486],{"class":73,"line":106},[71,18470,604],{"class":99},[71,18472,5383],{"class":507},[71,18474,610],{"class":99},[71,18476,8784],{"class":99},[71,18478,615],{"class":445},[71,18480,18481],{"class":88}," 42",[71,18483,6465],{"class":445},[71,18485,2878],{"class":99},[71,18487,687],{"class":445},[71,18489,18490,18492,18494,18497,18499,18501],{"class":73,"line":121},[71,18491,15222],{"class":88},[71,18493,475],{"class":445},[71,18495,18496],{"class":507},"resp",[71,18498,452],{"class":445},[71,18500,18481],{"class":88},[71,18502,765],{"class":445},[71,18504,18505],{"class":73,"line":259},[71,18506,559],{"class":445},[57,18508,18510],{"id":18509},"_154-mockmockall简例","15.4 Mock：mockall（简例）",[62,18512,18514],{"className":143,"code":18513,"language":145,"meta":67,"style":67},"[dev-dependencies]\nmockall = \"0.13\"\n",[52,18515,18516,18521],{"__ignoreMap":67},[71,18517,18518],{"class":73,"line":74},[71,18519,18520],{},"[dev-dependencies]\n",[71,18522,18523],{"class":73,"line":85},[71,18524,18525],{},"mockall = \"0.13\"\n",[62,18527,18529],{"className":427,"code":18528,"language":429,"meta":67,"style":67},"use mockall::{automock, predicate::*};\n\n#[automock]                        \u002F\u002F 自动生成 MockDb，可预设每个方法的返回值与调用次数\ntrait Db { fn get(&self, id: u64) -> Option\u003CString>; }\n\nfn find_name(db: &impl Db, id: u64) -> String {\n    db.get(id).unwrap_or(\"匿名\".into())\n}\n\n#[test]\nfn test_find() {\n    let mut mock = MockDb::new();\n    mock.expect_get()              \u002F\u002F 预期 get 会被调用\n        .with(eq(1u64))            \u002F\u002F 参数必须是 1\n        .times(1)\n        .returning(|_| Some(\"小明\".into()));\n    assert_eq!(find_name(&mock, 1), \"小明\");\n    \u002F\u002F 为什么 mock 而不是连真库？单元测试要快、稳定、可并行，\n    \u002F\u002F 数据库交互的正确性交给少量集成测试去验证\n}\n",[52,18530,18531,18554,18558,18572,18615,18619,18653,18683,18687,18691,18699,18710,18730,18747,18772,18785,18813,18839,18846,18853],{"__ignoreMap":67},[71,18532,18533,18535,18538,18540,18542,18545,18547,18550,18552],{"class":73,"line":74},[71,18534,436],{"class":99},[71,18536,18537],{"class":439}," mockall",[71,18539,442],{"class":99},[71,18541,446],{"class":445},[71,18543,18544],{"class":472},"automock",[71,18546,452],{"class":445},[71,18548,18549],{"class":439}," predicate",[71,18551,12113],{"class":99},[71,18553,458],{"class":445},[71,18555,18556],{"class":73,"line":85},[71,18557,464],{"emptyLinePlaceholder":463},[71,18559,18560,18562,18564,18566,18569],{"class":73,"line":106},[71,18561,469],{"class":445},[71,18563,18544],{"class":472},[71,18565,1337],{"class":445},[71,18567,18568],{"class":77},"                        \u002F\u002F",[71,18570,18571],{"class":81}," 自动生成 MockDb，可预设每个方法的返回值与调用次数\n",[71,18573,18574,18577,18580,18582,18584,18586,18588,18590,18592,18594,18596,18598,18600,18602,18604,18606,18608,18610,18613],{"class":73,"line":121},[71,18575,18576],{"class":99},"trait",[71,18578,18579],{"class":439}," Db",[71,18581,615],{"class":445},[71,18583,2793],{"class":99},[71,18585,6685],{"class":88},[71,18587,475],{"class":445},[71,18589,675],{"class":99},[71,18591,8109],{"class":507},[71,18593,452],{"class":445},[71,18595,2453],{"class":507},[71,18597,511],{"class":99},[71,18599,931],{"class":439},[71,18601,681],{"class":445},[71,18603,577],{"class":99},[71,18605,539],{"class":439},[71,18607,542],{"class":445},[71,18609,545],{"class":439},[71,18611,18612],{"class":445},">;",[71,18614,1180],{"class":445},[71,18616,18617],{"class":73,"line":259},[71,18618,464],{"emptyLinePlaceholder":463},[71,18620,18621,18623,18626,18628,18630,18632,18635,18637,18639,18641,18643,18645,18647,18649,18651],{"class":73,"line":273},[71,18622,568],{"class":99},[71,18624,18625],{"class":88}," find_name",[71,18627,475],{"class":445},[71,18629,10463],{"class":507},[71,18631,511],{"class":99},[71,18633,18634],{"class":99}," &impl",[71,18636,18579],{"class":439},[71,18638,452],{"class":445},[71,18640,2453],{"class":507},[71,18642,511],{"class":99},[71,18644,931],{"class":439},[71,18646,681],{"class":445},[71,18648,577],{"class":99},[71,18650,514],{"class":439},[71,18652,502],{"class":445},[71,18654,18655,18658,18660,18662,18664,18666,18668,18670,18672,18674,18677,18679,18681],{"class":73,"line":286},[71,18656,18657],{"class":507},"    db",[71,18659,626],{"class":99},[71,18661,5011],{"class":88},[71,18663,475],{"class":445},[71,18665,2424],{"class":507},[71,18667,681],{"class":445},[71,18669,626],{"class":99},[71,18671,7243],{"class":88},[71,18673,475],{"class":445},[71,18675,18676],{"class":92},"\"匿名\"",[71,18678,626],{"class":99},[71,18680,629],{"class":88},[71,18682,2500],{"class":445},[71,18684,18685],{"class":73,"line":300},[71,18686,559],{"class":445},[71,18688,18689],{"class":73,"line":316},[71,18690,464],{"emptyLinePlaceholder":463},[71,18692,18693,18695,18697],{"class":73,"line":330},[71,18694,469],{"class":445},[71,18696,18008],{"class":472},[71,18698,2785],{"class":445},[71,18700,18701,18703,18706,18708],{"class":73,"line":352},[71,18702,568],{"class":99},[71,18704,18705],{"class":88}," test_find",[71,18707,574],{"class":445},[71,18709,502],{"class":445},[71,18711,18712,18714,18716,18719,18721,18724,18726,18728],{"class":73,"line":601},[71,18713,604],{"class":99},[71,18715,3780],{"class":99},[71,18717,18718],{"class":507}," mock",[71,18720,610],{"class":99},[71,18722,18723],{"class":439}," MockDb",[71,18725,442],{"class":99},[71,18727,3876],{"class":88},[71,18729,1499],{"class":445},[71,18731,18732,18735,18737,18740,18742,18744],{"class":73,"line":656},[71,18733,18734],{"class":507},"    mock",[71,18736,626],{"class":99},[71,18738,18739],{"class":88},"expect_get",[71,18741,574],{"class":445},[71,18743,1927],{"class":77},[71,18745,18746],{"class":81}," 预期 get 会被调用\n",[71,18748,18749,18751,18754,18756,18759,18761,18763,18765,18767,18769],{"class":73,"line":690},[71,18750,1775],{"class":99},[71,18752,18753],{"class":88},"with",[71,18755,475],{"class":445},[71,18757,18758],{"class":88},"eq",[71,18760,475],{"class":445},[71,18762,2061],{"class":88},[71,18764,2266],{"class":439},[71,18766,1803],{"class":445},[71,18768,918],{"class":77},[71,18770,18771],{"class":81}," 参数必须是 1\n",[71,18773,18774,18776,18779,18781,18783],{"class":73,"line":719},[71,18775,1775],{"class":99},[71,18777,18778],{"class":88},"times",[71,18780,475],{"class":445},[71,18782,2061],{"class":88},[71,18784,1770],{"class":445},[71,18786,18787,18789,18792,18794,18796,18798,18800,18802,18804,18806,18808,18810],{"class":73,"line":724},[71,18788,1775],{"class":99},[71,18790,18791],{"class":88},"returning",[71,18793,475],{"class":445},[71,18795,7400],{"class":99},[71,18797,3584],{"class":507},[71,18799,7400],{"class":99},[71,18801,1301],{"class":439},[71,18803,475],{"class":445},[71,18805,2493],{"class":92},[71,18807,626],{"class":99},[71,18809,629],{"class":88},[71,18811,18812],{"class":445},"()));\n",[71,18814,18815,18817,18819,18822,18824,18826,18829,18831,18833,18835,18837],{"class":73,"line":732},[71,18816,15222],{"class":88},[71,18818,475],{"class":445},[71,18820,18821],{"class":88},"find_name",[71,18823,475],{"class":445},[71,18825,675],{"class":99},[71,18827,18828],{"class":507},"mock",[71,18830,452],{"class":445},[71,18832,4272],{"class":88},[71,18834,2398],{"class":445},[71,18836,623],{"class":92},[71,18838,765],{"class":445},[71,18840,18841,18843],{"class":73,"line":768},[71,18842,595],{"class":77},[71,18844,18845],{"class":81}," 为什么 mock 而不是连真库？单元测试要快、稳定、可并行，\n",[71,18847,18848,18850],{"class":73,"line":773},[71,18849,595],{"class":77},[71,18851,18852],{"class":81}," 数据库交互的正确性交给少量集成测试去验证\n",[71,18854,18855],{"class":73,"line":781},[71,18856,559],{"class":445},[23,18858],{},[26,18860,18862],{"id":18861},"附录如何为项目选库","附录：如何为项目选库",[14,18864,18865],{},[18,18866,18867],{},"凭这三条原则做绝大多数决策：",[18869,18870,18871,18877,18883],"ol",{},[15586,18872,18873,18876],{},[18,18874,18875],{},"看生态位","：Rust 生态每个领域通常有一个「事实标准」（serde、tokio、reqwest、clap、sqlx……）。没有特殊理由就用它——文档最多、Stack Overflow 答案最全、其他库的集成最好。",[15586,18878,18879,18882],{},[18,18880,18881],{},"看维护状态","：在 crates.io 看最近更新时间和下载量；在 GitHub 看 issue 响应。两年没更新且 issue 堆积的库要警惕。",[15586,18884,18885,18888,18889,18892],{},[18,18886,18887],{},"看编译期 vs 运行时的取舍","：Rust 社区的偏好是",[18,18890,18891],{},"能在编译期抓到的错误绝不拖到运行时","（sqlx 的编译期 SQL 校验、axum 的类型化提取器、serde 的 derive）。选库时倾向这种风格的，长期少踩坑。",[14,18894,18895],{},[18,18896,18897],{},"本项目速查表：",[1565,18899,18900,18910],{},[1568,18901,18902],{},[1571,18903,18904,18907],{},[1574,18905,18906],{},"需求",[1574,18908,18909],{},"用这个",[1584,18911,18912,18920,18928,18935,18942,18950,18958,18966,18974,18982,18990,18998,19006,19014,19021],{},[1571,18913,18914,18917],{},[1589,18915,18916],{},"JSON \u002F 配置序列化",[1589,18918,18919],{},"serde + serde_json \u002F toml",[1571,18921,18922,18925],{},[1589,18923,18924],{},"应用错误处理",[1589,18926,18927],{},"anyhow（库用 thiserror）",[1571,18929,18930,18933],{},[1589,18931,18932],{},"异步 \u002F 网络服务",[1589,18934,2777],{},[1571,18936,18937,18940],{},[1589,18938,18939],{},"调 HTTP API",[1589,18941,5744],{},[1571,18943,18944,18947],{},[1589,18945,18946],{},"写 HTTP 服务",[1589,18948,18949],{},"axum",[1571,18951,18952,18955],{},[1589,18953,18954],{},"命令行工具",[1589,18956,18957],{},"clap",[1571,18959,18960,18963],{},[1589,18961,18962],{},"日志",[1589,18964,18965],{},"tracing（简单场景 log + env_logger）",[1571,18967,18968,18971],{},[1589,18969,18970],{},"日期时间",[1589,18972,18973],{},"chrono",[1571,18975,18976,18979],{},[1589,18977,18978],{},"随机数",[1589,18980,18981],{},"rand",[1571,18983,18984,18987],{},[1589,18985,18986],{},"正则",[1589,18988,18989],{},"regex",[1571,18991,18992,18995],{},[1589,18993,18994],{},"迭代器不够用",[1589,18996,18997],{},"itertools",[1571,18999,19000,19003],{},[1589,19001,19002],{},"CPU 密集并行",[1589,19004,19005],{},"rayon",[1571,19007,19008,19011],{},[1589,19009,19010],{},"数据库",[1589,19012,19013],{},"sqlx（要 ORM 再看 sea-orm）",[1571,19015,19016,19019],{},[1589,19017,19018],{},"环境变量",[1589,19020,16632],{},[1571,19022,19023,19026],{},[1589,19024,19025],{},"UUID",[1589,19027,19028],{},"uuid",[11,19030,19031],{},[14,19032,19033,19034,19037],{},"最后一条建议：",[18,19035,19036],{},"不要一次全用","。新项目从「std + serde + tokio + 领域必需的一两个库」开始，用到什么加什么。Rust 的编译器会接住你，依赖越少，构建越快，心智越轻。",[19039,19040,19041],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sGUtj, html code.shiki .sGUtj{--shiki-light:#797593;--shiki-light-font-style:italic;--shiki-default:#797593;--shiki-default-font-style:italic;--shiki-dark:#908CAA;--shiki-dark-font-style:italic}html pre.shiki code .sNkb5, html code.shiki .sNkb5{--shiki-light:#9893A5;--shiki-light-font-style:italic;--shiki-default:#9893A5;--shiki-default-font-style:italic;--shiki-dark:#6E6A86;--shiki-dark-font-style:italic}html pre.shiki code .svtxk, html code.shiki .svtxk{--shiki-light:#D7827E;--shiki-default:#D7827E;--shiki-dark:#EA9A97}html pre.shiki code .sKSG5, html code.shiki .sKSG5{--shiki-light:#EA9D34;--shiki-default:#EA9D34;--shiki-dark:#F6C177}html pre.shiki code .sP2eF, html code.shiki .sP2eF{--shiki-light:#286983;--shiki-default:#286983;--shiki-dark:#3E8FB0}html pre.shiki code .sn0A_, html code.shiki .sn0A_{--shiki-light:#56949F;--shiki-default:#56949F;--shiki-dark:#9CCFD8}html pre.shiki code .sp1GA, html code.shiki .sp1GA{--shiki-light:#797593;--shiki-default:#797593;--shiki-dark:#908CAA}html pre.shiki code .sdIFE, html code.shiki .sdIFE{--shiki-light:#575279;--shiki-default:#575279;--shiki-dark:#E0DEF4}html pre.shiki code .s00_H, html code.shiki .s00_H{--shiki-light:#575279;--shiki-light-font-style:italic;--shiki-default:#575279;--shiki-default-font-style:italic;--shiki-dark:#E0DEF4;--shiki-dark-font-style:italic}",{"title":67,"searchDepth":121,"depth":121,"links":19043},[19044,19048,19056,19061,19069,19076,19083,19088,19093,19098,19099,19103,19104,19110,19115,19127,19133],{"id":28,"depth":85,"text":29,"children":19045},[19046,19047],{"id":59,"depth":106,"text":60},{"id":188,"depth":106,"text":189},{"id":380,"depth":85,"text":381,"children":19049},[19050,19051,19052,19053,19055],{"id":384,"depth":106,"text":385},{"id":405,"depth":106,"text":406},{"id":862,"depth":106,"text":863},{"id":1209,"depth":106,"text":19054},"1.4 处理不确定结构的 JSON：Value",{"id":1443,"depth":106,"text":1444},{"id":1544,"depth":85,"text":1545,"children":19057},[19058,19059,19060],{"id":1548,"depth":106,"text":1549},{"id":1622,"depth":106,"text":1623},{"id":2160,"depth":106,"text":2161},{"id":2710,"depth":85,"text":2711,"children":19062},[19063,19064,19065,19066,19067,19068],{"id":2714,"depth":106,"text":2715},{"id":2757,"depth":106,"text":2758},{"id":2945,"depth":106,"text":2946},{"id":3389,"depth":106,"text":3390},{"id":3619,"depth":106,"text":3620},{"id":4010,"depth":106,"text":4011},{"id":4807,"depth":85,"text":4808,"children":19070},[19071,19072,19073,19074,19075],{"id":4811,"depth":106,"text":4812},{"id":4839,"depth":106,"text":4840},{"id":5176,"depth":106,"text":5177},{"id":5683,"depth":106,"text":5684},{"id":5950,"depth":106,"text":5951},{"id":6250,"depth":85,"text":6251,"children":19077},[19078,19079,19080,19081,19082],{"id":6254,"depth":106,"text":6255},{"id":6304,"depth":106,"text":6305},{"id":6842,"depth":106,"text":6843},{"id":7941,"depth":106,"text":7942},{"id":8876,"depth":106,"text":8877},{"id":9034,"depth":85,"text":9035,"children":19084},[19085,19086,19087],{"id":9038,"depth":106,"text":9039},{"id":9077,"depth":106,"text":9078},{"id":9922,"depth":106,"text":9923},{"id":10076,"depth":85,"text":10077,"children":19089},[19090,19091,19092],{"id":10080,"depth":106,"text":10081},{"id":10107,"depth":106,"text":10108},{"id":10364,"depth":106,"text":10365},{"id":10858,"depth":85,"text":10859,"children":19094},[19095,19096,19097],{"id":10862,"depth":106,"text":10863},{"id":10886,"depth":106,"text":10887},{"id":11551,"depth":106,"text":11552},{"id":12054,"depth":85,"text":12055},{"id":12826,"depth":85,"text":12827,"children":19100},[19101,19102],{"id":12844,"depth":106,"text":12845},{"id":13671,"depth":106,"text":13672},{"id":13865,"depth":85,"text":13866},{"id":14892,"depth":85,"text":14893,"children":19105},[19106,19107,19109],{"id":14896,"depth":106,"text":14897},{"id":15465,"depth":106,"text":19108},"12.2 两个独立任务的并行：join",{"id":15580,"depth":106,"text":15581},{"id":15613,"depth":85,"text":15614,"children":19111},[19112,19113,19114],{"id":15617,"depth":106,"text":15618},{"id":15659,"depth":106,"text":15660},{"id":16538,"depth":106,"text":16539},{"id":16591,"depth":85,"text":16592,"children":19116},[19117,19118,19119,19120,19121,19122,19123,19124,19125,19126],{"id":16598,"depth":106,"text":16599},{"id":16655,"depth":106,"text":16656},{"id":16724,"depth":106,"text":16725},{"id":16831,"depth":106,"text":16832},{"id":16977,"depth":106,"text":16978},{"id":17149,"depth":106,"text":17150},{"id":17243,"depth":106,"text":17244},{"id":17496,"depth":106,"text":17497},{"id":17721,"depth":106,"text":17722},{"id":17846,"depth":106,"text":17847},{"id":17944,"depth":85,"text":17945,"children":19128},[19129,19130,19131,19132],{"id":17948,"depth":106,"text":17949},{"id":18348,"depth":106,"text":18349},{"id":18429,"depth":106,"text":18430},{"id":18509,"depth":106,"text":18510},{"id":18861,"depth":85,"text":18862},"2026-09-28","md",{},"\u002Fblog\u002Fstack\u002Frust-crate-in-action",{"title":5,"description":67},"blog\u002Fstack\u002Frust-crate-in-action",[],"PXv_4pVJ5lx9RSpZsK_lz77pLhhZ5wo7hSLtKcKwKSk",1792899441688]