[{"data":1,"prerenderedAt":20102},["ShallowReactive",2],{"stack-index":3},[4,19143,19855],{"id":5,"title":6,"author":7,"body":8,"category":7,"date":19135,"description":68,"extension":19136,"image":7,"meta":19137,"navigation":464,"path":19138,"recommend":7,"seo":19139,"stem":19140,"tags":19141,"updated":7,"__hash__":19142},"stack\u002Fblog\u002Fstack\u002Frust-crate-in-action.md","Rust 常用库实战教程",null,{"type":9,"value":10,"toc":19043},"minimark",[11,23,26,31,57,62,135,142,169,187,191,364,377,379,383,387,390,404,408,426,861,865,1182,1208,1214,1217,1442,1446,1449,1464,1535,1541,1543,1547,1551,1562,1565,1621,1625,1639,2147,2159,2163,2177,2668,2696,2707,2709,2713,2717,2736,2742,2756,2760,2944,2948,3372,3388,3392,3612,3618,3622,3927,3938,3996,4009,4013,4791,4804,4806,4810,4814,4817,4838,4842,5175,5179,5676,5682,5686,5949,5953,6247,6249,6253,6257,6264,6290,6303,6307,6831,6841,6845,7940,7944,8875,8879,9031,9033,9037,9041,9056,9070,9076,9080,9862,9865,9921,9925,10073,10075,10079,10083,10100,10106,10110,10129,10298,10353,10363,10367,10377,10396,10811,10816,10823,10836,10855,10857,10861,10865,10871,10885,10889,11544,11550,11554,12035,12051,12053,12057,12071,12095,12803,12823,12825,12829,12843,12847,13670,13674,13848,13862,13864,13868,13871,13885,14875,14889,14891,14895,14899,14909,14923,15439,15464,15470,15579,15583,15610,15612,15616,15620,15635,15658,15662,16531,16537,16541,16574,16588,16590,16594,16597,16601,16610,16654,16658,16667,16723,16727,16830,16834,16849,16976,16980,16989,17148,17152,17161,17242,17246,17255,17495,17499,17508,17720,17724,17733,17845,17849,17941,17943,17947,17951,18347,18351,18366,18428,18432,18508,18512,18527,18858,18860,18864,18869,18894,18899,19030,19039],[12,13,14],"blockquote",{},[15,16,17,18,22],"p",{},"面向已有 Rust 基础语法、想快速上手工程开发的读者。\n每个库都包含：",[19,20,21],"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 等）。",[24,25],"hr",{},[27,28,30],"h2",{"id":29},"第-0-章-开始之前cargo-与依赖管理","第 0 章 开始之前：Cargo 与依赖管理",[15,32,33,34,37,38,45,46,51,52,56],{},"Rust 的库叫 ",[19,35,36],{},"crate","，统一发布在 ",[39,40,44],"a",{"href":41,"rel":42},"https:\u002F\u002Fcrates.io",[43],"nofollow","crates.io","，文档统一托管在 ",[39,47,50],{"href":48,"rel":49},"https:\u002F\u002Fdocs.rs",[43],"docs.rs","。\n看某个库的用法，直接访问 ",[53,54,55],"code",{},"https:\u002F\u002Fdocs.rs\u002F\u003C库名>"," 即可，这是 Rust 生态最重要的习惯。",[58,59,61],"h3",{"id":60},"_01-添加依赖","0.1 添加依赖",[63,64,69],"pre",{"className":65,"code":66,"language":67,"meta":68,"style":68},"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","",[53,70,71,84,105,120],{"__ignoreMap":68},[72,73,76,80],"span",{"class":74,"line":75},"line",1,[72,77,79],{"class":78},"sGUtj","#",[72,81,83],{"class":82},"sNkb5"," 现代方式（推荐）：用 cargo add，自动写入最新兼容版本\n",[72,85,87,91,95,98,102],{"class":74,"line":86},2,[72,88,90],{"class":89},"svtxk","cargo",[72,92,94],{"class":93},"sKSG5"," add",[72,96,97],{"class":93}," serde",[72,99,101],{"class":100},"sP2eF"," --features",[72,103,104],{"class":93}," derive\n",[72,106,108,110,112,115,117],{"class":74,"line":107},3,[72,109,90],{"class":89},[72,111,94],{"class":93},[72,113,114],{"class":93}," tokio",[72,116,101],{"class":100},[72,118,119],{"class":93}," full\n",[72,121,123,125,127,130,132],{"class":74,"line":122},4,[72,124,90],{"class":89},[72,126,94],{"class":93},[72,128,129],{"class":93}," reqwest",[72,131,101],{"class":100},[72,133,134],{"class":93}," json\n",[15,136,137,138,141],{},"也可以手动编辑 ",[53,139,140],{},"Cargo.toml","：",[63,143,147],{"className":144,"code":145,"language":146,"meta":68,"style":68},"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",[53,148,149,154,159,164],{"__ignoreMap":68},[72,150,151],{"class":74,"line":75},[72,152,153],{},"[dependencies]\n",[72,155,156],{"class":74,"line":86},[72,157,158],{},"serde = { version = \"1\", features = [\"derive\"] }\n",[72,160,161],{"class":74,"line":107},[72,162,163],{},"tokio = { version = \"1\", features = [\"full\"] }\n",[72,165,166],{"class":74,"line":122},[72,167,168],{},"reqwest = { version = \"0.12\", features = [\"json\"] }\n",[15,170,171,174,175,178,179,182,183,186],{},[19,172,173],{},"为什么强调 features？"," Rust crate 普遍把非核心功能放在 feature 后面（比如 serde 的 ",[53,176,177],{},"derive","、tokio 的 ",[53,180,181],{},"full","），默认不开启。这样编译时只编译你真正用到的代码，",[19,184,185],{},"编译更快、产物更小","。这是 Rust「零成本抽象」哲学在依赖管理上的体现：不为用不到的东西付出代价。",[58,188,190],{"id":189},"_02-常用-cargo-命令","0.2 常用 Cargo 命令",[63,192,194],{"className":65,"code":193,"language":67,"meta":68,"style":68},"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",[53,195,196,212,230,243,258,272,285,299,315,329,351],{"__ignoreMap":68},[72,197,198,200,203,206,209],{"class":74,"line":75},[72,199,90],{"class":89},[72,201,202],{"class":93}," new",[72,204,205],{"class":93}," my_app",[72,207,208],{"class":78},"        #",[72,210,211],{"class":82}," 新建二进制项目\n",[72,213,214,216,218,221,224,227],{"class":74,"line":86},[72,215,90],{"class":89},[72,217,202],{"class":93},[72,219,220],{"class":93}," my_lib",[72,222,223],{"class":100}," --lib",[72,225,226],{"class":78},"  #",[72,228,229],{"class":82}," 新建库项目\n",[72,231,232,234,237,240],{"class":74,"line":107},[72,233,90],{"class":89},[72,235,236],{"class":93}," build",[72,238,239],{"class":78},"             #",[72,241,242],{"class":82}," 调试构建（快，无优化）\n",[72,244,245,247,249,252,255],{"class":74,"line":122},[72,246,90],{"class":89},[72,248,236],{"class":93},[72,250,251],{"class":100}," --release",[72,253,254],{"class":78},"   #",[72,256,257],{"class":82}," 发布构建（慢，有优化）\n",[72,259,261,263,266,269],{"class":74,"line":260},5,[72,262,90],{"class":89},[72,264,265],{"class":93}," run",[72,267,268],{"class":78},"               #",[72,270,271],{"class":82}," 构建并运行\n",[72,273,275,277,280,282],{"class":74,"line":274},6,[72,276,90],{"class":89},[72,278,279],{"class":93}," check",[72,281,239],{"class":78},[72,283,284],{"class":82}," 只检查不生成二进制，改代码时最快\n",[72,286,288,290,293,296],{"class":74,"line":287},7,[72,289,90],{"class":89},[72,291,292],{"class":93}," test",[72,294,295],{"class":78},"              #",[72,297,298],{"class":82}," 运行测试\n",[72,300,302,304,307,310,312],{"class":74,"line":301},8,[72,303,90],{"class":89},[72,305,306],{"class":93}," doc",[72,308,309],{"class":100}," --open",[72,311,208],{"class":78},[72,313,314],{"class":82}," 生成并打开本项目（含依赖）的文档\n",[72,316,318,320,323,326],{"class":74,"line":317},9,[72,319,90],{"class":89},[72,321,322],{"class":93}," update",[72,324,325],{"class":78},"            #",[72,327,328],{"class":82}," 在语义化版本允许范围内升级依赖\n",[72,330,332,334,336,339,342,345,348],{"class":74,"line":331},10,[72,333,90],{"class":89},[72,335,94],{"class":93},[72,337,338],{"class":93}," \u002F",[72,340,341],{"class":93}," cargo",[72,343,344],{"class":93}," rm",[72,346,347],{"class":78},"    #",[72,349,350],{"class":82}," 增删依赖\n",[72,352,354,356,359,361],{"class":74,"line":353},11,[72,355,90],{"class":89},[72,357,358],{"class":93}," tree",[72,360,295],{"class":78},[72,362,363],{"class":82}," 查看依赖树，排查版本冲突\n",[12,365,366],{},[15,367,368,369,372,373,376],{},"经验：写代码时多用 ",[53,370,371],{},"cargo check","，秒级反馈；需要运行时再 ",[53,374,375],{},"cargo run","。",[24,378],{},[27,380,382],{"id":381},"第-1-章-serde-serde_json序列化与反序列化","第 1 章 serde \u002F serde_json：序列化与反序列化",[58,384,386],{"id":385},"_11-它解决什么问题","1.1 它解决什么问题",[15,388,389],{},"程序里的结构体和外部世界的 JSON\u002FTOML\u002FYAML 之间需要互相转换。serde 是 Rust 生态事实上的标准，几乎所有库（reqwest、axum、sqlx、config 解析……）都建立在它之上。",[15,391,392,395,396,399,400,403],{},[19,393,394],{},"为什么要用 derive 而不是手写？"," 手写序列化代码冗长且容易和结构体定义脱节（改了字段忘了改解析逻辑）。",[53,397,398],{},"#[derive(Serialize, Deserialize)]"," 在编译期自动生成转换代码，",[19,401,402],{},"结构体定义即契约","，改一处即可，且零运行时开销。",[58,405,407],{"id":406},"_12-最小案例","1.2 最小案例",[63,409,411],{"className":144,"code":410,"language":146,"meta":68,"style":68},"[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n",[53,412,413,417,421],{"__ignoreMap":68},[72,414,415],{"class":74,"line":75},[72,416,153],{},[72,418,419],{"class":74,"line":86},[72,420,158],{},[72,422,423],{"class":74,"line":107},[72,424,425],{},"serde_json = \"1\"\n",[63,427,431],{"className":428,"code":429,"language":430,"meta":68,"style":68},"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",[53,432,433,460,466,493,504,519,531,556,561,565,592,600,655,689,718,723,731,767,772,780,795,827,847,856],{"__ignoreMap":68},[72,434,435,438,441,444,448,451,454,457],{"class":74,"line":75},[72,436,437],{"class":100},"use",[72,439,97],{"class":440},"sn0A_",[72,442,443],{"class":100},"::",[72,445,447],{"class":446},"sp1GA","{",[72,449,450],{"class":440},"Deserialize",[72,452,453],{"class":446},",",[72,455,456],{"class":440}," Serialize",[72,458,459],{"class":446},"};\n",[72,461,462],{"class":74,"line":86},[72,463,465],{"emptyLinePlaceholder":464},true,"\n",[72,467,468,471,474,477,480,482,485,487,490],{"class":74,"line":107},[72,469,470],{"class":446},"#[",[72,472,177],{"class":473},"sdIFE",[72,475,476],{"class":446},"(",[72,478,479],{"class":440},"Serialize",[72,481,453],{"class":446},[72,483,484],{"class":440}," Deserialize",[72,486,453],{"class":446},[72,488,489],{"class":440}," Debug",[72,491,492],{"class":446},")]\n",[72,494,495,498,501],{"class":74,"line":122},[72,496,497],{"class":100},"struct",[72,499,500],{"class":440}," User",[72,502,503],{"class":446}," {\n",[72,505,506,510,513,516],{"class":74,"line":260},[72,507,509],{"class":508},"s00_H","    name",[72,511,512],{"class":100},":",[72,514,515],{"class":440}," String",[72,517,518],{"class":446},",\n",[72,520,521,524,526,529],{"class":74,"line":274},[72,522,523],{"class":508},"    age",[72,525,512],{"class":100},[72,527,528],{"class":440}," u8",[72,530,518],{"class":446},[72,532,533,536,538,541,544,547,550,553],{"class":74,"line":287},[72,534,535],{"class":508},"    email",[72,537,512],{"class":100},[72,539,540],{"class":440}," Option",[72,542,543],{"class":446},"\u003C",[72,545,546],{"class":440},"String",[72,548,549],{"class":446},">,",[72,551,552],{"class":78}," \u002F\u002F",[72,554,555],{"class":82}," 可空字段用 Option\n",[72,557,558],{"class":74,"line":301},[72,559,560],{"class":446},"}\n",[72,562,563],{"class":74,"line":317},[72,564,465],{"emptyLinePlaceholder":464},[72,566,567,570,573,576,579,582,584,587,590],{"class":74,"line":331},[72,568,569],{"class":100},"fn",[72,571,572],{"class":89}," main",[72,574,575],{"class":446},"()",[72,577,578],{"class":100}," ->",[72,580,581],{"class":440}," serde_json",[72,583,443],{"class":100},[72,585,586],{"class":440},"Result",[72,588,589],{"class":446},"\u003C()>",[72,591,503],{"class":446},[72,593,594,597],{"class":74,"line":353},[72,595,596],{"class":78},"    \u002F\u002F",[72,598,599],{"class":82}," 结构体 -> JSON 字符串\n",[72,601,603,606,609,612,614,617,620,622,625,628,631,634,637,639,642,644,647,649,652],{"class":74,"line":602},12,[72,604,605],{"class":100},"    let",[72,607,608],{"class":508}," u",[72,610,611],{"class":100}," =",[72,613,500],{"class":440},[72,615,616],{"class":446}," {",[72,618,619],{"class":508}," name",[72,621,512],{"class":100},[72,623,624],{"class":93}," \"小明\"",[72,626,627],{"class":100},".",[72,629,630],{"class":89},"into",[72,632,633],{"class":446},"(),",[72,635,636],{"class":508}," age",[72,638,512],{"class":100},[72,640,641],{"class":89}," 20",[72,643,453],{"class":446},[72,645,646],{"class":508}," email",[72,648,512],{"class":100},[72,650,651],{"class":440}," None",[72,653,654],{"class":446}," };\n",[72,656,658,660,663,665,667,669,672,674,677,680,683,686],{"class":74,"line":657},13,[72,659,605],{"class":100},[72,661,662],{"class":508}," s",[72,664,611],{"class":100},[72,666,581],{"class":440},[72,668,443],{"class":100},[72,670,671],{"class":89},"to_string",[72,673,476],{"class":446},[72,675,676],{"class":100},"&",[72,678,679],{"class":508},"u",[72,681,682],{"class":446},")",[72,684,685],{"class":100},"?",[72,687,688],{"class":446},";\n",[72,690,692,695,697,700,702,705,708,710,713,715],{"class":74,"line":691},14,[72,693,694],{"class":89},"    println!",[72,696,476],{"class":446},[72,698,699],{"class":93},"\"",[72,701,447],{"class":446},[72,703,704],{"class":93},"s",[72,706,707],{"class":446},"}",[72,709,699],{"class":93},[72,711,712],{"class":446},");",[72,714,552],{"class":78},[72,716,717],{"class":82}," {\"name\":\"小明\",\"age\":20,\"email\":null}\n",[72,719,721],{"class":74,"line":720},15,[72,722,465],{"emptyLinePlaceholder":464},[72,724,726,728],{"class":74,"line":725},16,[72,727,596],{"class":78},[72,729,730],{"class":82}," 美化输出\n",[72,732,734,736,738,740,743,745,747,749,751,754,756,758,760,762,764],{"class":74,"line":733},17,[72,735,694],{"class":89},[72,737,476],{"class":446},[72,739,699],{"class":93},[72,741,742],{"class":446},"{}",[72,744,699],{"class":93},[72,746,453],{"class":446},[72,748,581],{"class":440},[72,750,443],{"class":100},[72,752,753],{"class":89},"to_string_pretty",[72,755,476],{"class":446},[72,757,676],{"class":100},[72,759,679],{"class":508},[72,761,682],{"class":446},[72,763,685],{"class":100},[72,765,766],{"class":446},");\n",[72,768,770],{"class":74,"line":769},18,[72,771,465],{"emptyLinePlaceholder":464},[72,773,775,777],{"class":74,"line":774},19,[72,776,596],{"class":78},[72,778,779],{"class":82}," JSON 字符串 -> 结构体\n",[72,781,783,785,788,790,793],{"class":74,"line":782},20,[72,784,605],{"class":100},[72,786,787],{"class":508}," raw",[72,789,611],{"class":100},[72,791,792],{"class":93}," r#\"{\"name\":\"小红\",\"age\":18,\"email\":\"a@b.com\"}\"#",[72,794,688],{"class":446},[72,796,798,800,803,805,807,809,811,813,816,818,821,823,825],{"class":74,"line":797},21,[72,799,605],{"class":100},[72,801,802],{"class":508}," v",[72,804,512],{"class":100},[72,806,500],{"class":440},[72,808,611],{"class":100},[72,810,581],{"class":440},[72,812,443],{"class":100},[72,814,815],{"class":89},"from_str",[72,817,476],{"class":446},[72,819,820],{"class":508},"raw",[72,822,682],{"class":446},[72,824,685],{"class":100},[72,826,688],{"class":446},[72,828,830,832,834,836,838,841,843,845],{"class":74,"line":829},22,[72,831,694],{"class":89},[72,833,476],{"class":446},[72,835,699],{"class":93},[72,837,447],{"class":446},[72,839,840],{"class":93},"v:?",[72,842,707],{"class":446},[72,844,699],{"class":93},[72,846,766],{"class":446},[72,848,850,853],{"class":74,"line":849},23,[72,851,852],{"class":440},"    Ok",[72,854,855],{"class":446},"(())\n",[72,857,859],{"class":74,"line":858},24,[72,860,560],{"class":446},[58,862,864],{"id":863},"_13-常用字段属性","1.3 常用字段属性",[63,866,868],{"className":428,"code":867,"language":430,"meta":68,"style":68},"#[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",[53,869,870,886,895,923,935,939,958,975,979,1003,1014,1018,1037,1048,1052,1075,1090,1094,1112,1147,1151,1155],{"__ignoreMap":68},[72,871,872,874,876,878,880,882,884],{"class":74,"line":75},[72,873,470],{"class":446},[72,875,177],{"class":473},[72,877,476],{"class":446},[72,879,479],{"class":440},[72,881,453],{"class":446},[72,883,484],{"class":440},[72,885,492],{"class":446},[72,887,888,890,893],{"class":74,"line":86},[72,889,497],{"class":100},[72,891,892],{"class":440}," Article",[72,894,503],{"class":446},[72,896,897,900,903,905,908,911,914,917,920],{"class":74,"line":107},[72,898,899],{"class":446},"    #[",[72,901,902],{"class":473},"serde",[72,904,476],{"class":446},[72,906,907],{"class":473},"rename ",[72,909,910],{"class":100},"=",[72,912,913],{"class":93}," \"id\"",[72,915,916],{"class":446},")]",[72,918,919],{"class":78},"            \u002F\u002F",[72,921,922],{"class":82}," JSON 里的字段名与 Rust 字段名不同\n",[72,924,925,928,930,933],{"class":74,"line":122},[72,926,927],{"class":508},"    article_id",[72,929,512],{"class":100},[72,931,932],{"class":440}," u64",[72,934,518],{"class":446},[72,936,937],{"class":74,"line":260},[72,938,465],{"emptyLinePlaceholder":464},[72,940,941,943,945,947,950,952,955],{"class":74,"line":274},[72,942,899],{"class":446},[72,944,902],{"class":473},[72,946,476],{"class":446},[72,948,949],{"class":473},"default",[72,951,916],{"class":446},[72,953,954],{"class":78},"                   \u002F\u002F",[72,956,957],{"class":82}," JSON 缺这个字段时用 Default 值而不是报错\n",[72,959,960,963,965,968,970,972],{"class":74,"line":287},[72,961,962],{"class":508},"    tags",[72,964,512],{"class":100},[72,966,967],{"class":440}," Vec",[72,969,543],{"class":446},[72,971,546],{"class":440},[72,973,974],{"class":446},">,\n",[72,976,977],{"class":74,"line":301},[72,978,465],{"emptyLinePlaceholder":464},[72,980,981,983,985,987,990,992,995,997,1000],{"class":74,"line":317},[72,982,899],{"class":446},[72,984,902],{"class":473},[72,986,476],{"class":446},[72,988,989],{"class":473},"default ",[72,991,910],{"class":100},[72,993,994],{"class":93}," \"default_status\"",[72,996,916],{"class":446},[72,998,999],{"class":78},"\u002F\u002F",[72,1001,1002],{"class":82}," 缺省值用指定函数\n",[72,1004,1005,1008,1010,1012],{"class":74,"line":331},[72,1006,1007],{"class":508},"    status",[72,1009,512],{"class":100},[72,1011,515],{"class":440},[72,1013,518],{"class":446},[72,1015,1016],{"class":74,"line":353},[72,1017,465],{"emptyLinePlaceholder":464},[72,1019,1020,1022,1024,1026,1029,1031,1034],{"class":74,"line":602},[72,1021,899],{"class":446},[72,1023,902],{"class":473},[72,1025,476],{"class":446},[72,1027,1028],{"class":473},"skip_serializing",[72,1030,916],{"class":446},[72,1032,1033],{"class":78},"          \u002F\u002F",[72,1035,1036],{"class":82}," 只反序列化，不序列化（如密码）\n",[72,1038,1039,1042,1044,1046],{"class":74,"line":657},[72,1040,1041],{"class":508},"    password",[72,1043,512],{"class":100},[72,1045,515],{"class":440},[72,1047,518],{"class":446},[72,1049,1050],{"class":74,"line":691},[72,1051,465],{"emptyLinePlaceholder":464},[72,1053,1054,1056,1058,1060,1063,1065,1068,1070,1072],{"class":74,"line":720},[72,1055,899],{"class":446},[72,1057,902],{"class":473},[72,1059,476],{"class":446},[72,1061,1062],{"class":473},"skip_serializing_if ",[72,1064,910],{"class":100},[72,1066,1067],{"class":93}," \"Option::is_none\"",[72,1069,916],{"class":446},[72,1071,552],{"class":78},[72,1073,1074],{"class":82}," None 时不输出该字段，而不是输出 null\n",[72,1076,1077,1080,1082,1084,1086,1088],{"class":74,"line":725},[72,1078,1079],{"class":508},"    avatar",[72,1081,512],{"class":100},[72,1083,540],{"class":440},[72,1085,543],{"class":446},[72,1087,546],{"class":440},[72,1089,974],{"class":446},[72,1091,1092],{"class":74,"line":733},[72,1093,465],{"emptyLinePlaceholder":464},[72,1095,1096,1098,1100,1102,1105,1107,1109],{"class":74,"line":769},[72,1097,899],{"class":446},[72,1099,902],{"class":473},[72,1101,476],{"class":446},[72,1103,1104],{"class":473},"flatten",[72,1106,916],{"class":446},[72,1108,954],{"class":78},[72,1110,1111],{"class":82}," 把内嵌结构体的字段拍平到本层\n",[72,1113,1114,1117,1119,1122,1124,1127,1129,1132,1134,1136,1138,1140,1142,1145],{"class":74,"line":774},[72,1115,1116],{"class":508},"    extra",[72,1118,512],{"class":100},[72,1120,1121],{"class":440}," std",[72,1123,443],{"class":100},[72,1125,1126],{"class":440},"collections",[72,1128,443],{"class":100},[72,1130,1131],{"class":440},"HashMap",[72,1133,543],{"class":446},[72,1135,546],{"class":440},[72,1137,453],{"class":446},[72,1139,581],{"class":508},[72,1141,443],{"class":100},[72,1143,1144],{"class":440},"Value",[72,1146,974],{"class":446},[72,1148,1149],{"class":74,"line":782},[72,1150,560],{"class":446},[72,1152,1153],{"class":74,"line":797},[72,1154,465],{"emptyLinePlaceholder":464},[72,1156,1157,1159,1162,1164,1166,1168,1170,1173,1175,1177,1179],{"class":74,"line":829},[72,1158,569],{"class":100},[72,1160,1161],{"class":89}," default_status",[72,1163,575],{"class":446},[72,1165,578],{"class":100},[72,1167,515],{"class":440},[72,1169,616],{"class":446},[72,1171,1172],{"class":93}," \"draft\"",[72,1174,627],{"class":100},[72,1176,630],{"class":89},[72,1178,575],{"class":446},[72,1180,1181],{"class":446}," }\n",[15,1183,1184,1195,1196,1199,1200,1203,1204,1207],{},[19,1185,1186,1187,1190,1191,1194],{},"为什么 ",[53,1188,1189],{},"Option"," 字段还要加 ",[53,1192,1193],{},"skip_serializing_if","？"," 默认 ",[53,1197,1198],{},"None"," 会序列化成 ",[53,1201,1202],{},"\"avatar\": null","，但很多前端\u002F接口约定「没有就不给这个 key」。这个属性让输出更干净，也避免对方把 ",[53,1205,1206],{},"null"," 和「不存在」混为一谈。",[58,1209,1211,1212],{"id":1210},"_14-处理不确定结构的-jsonvalue","1.4 处理不确定结构的 JSON：",[53,1213,1144],{},[15,1215,1216],{},"爬第三方接口时，字段经常不稳定，不值得定义完整结构体：",[63,1218,1220],{"className":428,"code":1219,"language":430,"meta":68,"style":68},"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",[53,1221,1222,1234,1238,1252,1281,1285,1292,1348,1367,1371,1375,1382,1401,1413,1425,1437],{"__ignoreMap":68},[72,1223,1224,1226,1228,1230,1232],{"class":74,"line":75},[72,1225,437],{"class":100},[72,1227,581],{"class":440},[72,1229,443],{"class":100},[72,1231,1144],{"class":440},[72,1233,688],{"class":446},[72,1235,1236],{"class":74,"line":86},[72,1237,465],{"emptyLinePlaceholder":464},[72,1239,1240,1243,1245,1247,1250],{"class":74,"line":107},[72,1241,1242],{"class":100},"let",[72,1244,787],{"class":508},[72,1246,611],{"class":100},[72,1248,1249],{"class":93}," r#\"{\"code\":0,\"data\":{\"users\":[{\"name\":\"a\"},{\"name\":\"b\"}]}}\"#",[72,1251,688],{"class":446},[72,1253,1254,1256,1258,1260,1263,1265,1267,1269,1271,1273,1275,1277,1279],{"class":74,"line":122},[72,1255,1242],{"class":100},[72,1257,802],{"class":508},[72,1259,512],{"class":100},[72,1261,1262],{"class":440}," Value",[72,1264,611],{"class":100},[72,1266,581],{"class":440},[72,1268,443],{"class":100},[72,1270,815],{"class":89},[72,1272,476],{"class":446},[72,1274,820],{"class":508},[72,1276,682],{"class":446},[72,1278,685],{"class":100},[72,1280,688],{"class":446},[72,1282,1283],{"class":74,"line":260},[72,1284,465],{"emptyLinePlaceholder":464},[72,1286,1287,1289],{"class":74,"line":274},[72,1288,999],{"class":78},[72,1290,1291],{"class":82}," 用链式索引取值，类型不符返回 None 而不是 panic\n",[72,1293,1294,1297,1300,1303,1305,1308,1310,1312,1314,1317,1320,1323,1326,1328,1331,1333,1336,1339,1341,1344,1346],{"class":74,"line":287},[72,1295,1296],{"class":100},"if",[72,1298,1299],{"class":100}," let",[72,1301,1302],{"class":440}," Some",[72,1304,476],{"class":446},[72,1306,1307],{"class":508},"name",[72,1309,682],{"class":446},[72,1311,611],{"class":100},[72,1313,802],{"class":508},[72,1315,1316],{"class":446},"[",[72,1318,1319],{"class":93},"\"data\"",[72,1321,1322],{"class":446},"][",[72,1324,1325],{"class":93},"\"users\"",[72,1327,1322],{"class":446},[72,1329,1330],{"class":89},"0",[72,1332,1322],{"class":446},[72,1334,1335],{"class":93},"\"name\"",[72,1337,1338],{"class":446},"]",[72,1340,627],{"class":100},[72,1342,1343],{"class":89},"as_str",[72,1345,575],{"class":446},[72,1347,503],{"class":446},[72,1349,1350,1352,1354,1357,1359,1361,1363,1365],{"class":74,"line":301},[72,1351,694],{"class":89},[72,1353,476],{"class":446},[72,1355,1356],{"class":93},"\"第一个用户：",[72,1358,447],{"class":446},[72,1360,1307],{"class":93},[72,1362,707],{"class":446},[72,1364,699],{"class":93},[72,1366,766],{"class":446},[72,1368,1369],{"class":74,"line":317},[72,1370,560],{"class":446},[72,1372,1373],{"class":74,"line":331},[72,1374,465],{"emptyLinePlaceholder":464},[72,1376,1377,1379],{"class":74,"line":353},[72,1378,999],{"class":78},[72,1380,1381],{"class":82}," json! 宏快速构造 JSON\n",[72,1383,1384,1386,1389,1391,1393,1395,1398],{"class":74,"line":602},[72,1385,1242],{"class":100},[72,1387,1388],{"class":508}," payload",[72,1390,611],{"class":100},[72,1392,581],{"class":440},[72,1394,443],{"class":100},[72,1396,1397],{"class":89},"json!",[72,1399,1400],{"class":446},"({\n",[72,1402,1403,1406,1408,1411],{"class":74,"line":657},[72,1404,1405],{"class":93},"    \"title\"",[72,1407,512],{"class":100},[72,1409,1410],{"class":93}," \"hello\"",[72,1412,518],{"class":446},[72,1414,1415,1418,1420,1423],{"class":74,"line":691},[72,1416,1417],{"class":93},"    \"count\"",[72,1419,512],{"class":100},[72,1421,1422],{"class":89}," 3",[72,1424,518],{"class":446},[72,1426,1427,1430,1432,1435],{"class":74,"line":720},[72,1428,1429],{"class":93},"    \"ok\"",[72,1431,512],{"class":100},[72,1433,1434],{"class":89}," true",[72,1436,518],{"class":446},[72,1438,1439],{"class":74,"line":725},[72,1440,1441],{"class":446},"});\n",[58,1443,1445],{"id":1444},"_15-其他格式","1.5 其他格式",[15,1447,1448],{},"serde 是「中间表示」层，换个后端 crate 就支持别的格式，用法完全一致：",[63,1450,1452],{"className":144,"code":1451,"language":146,"meta":68,"style":68},"toml = \"0.8\"          # TOML\nserde_yaml = \"0.9\"    # YAML\n",[53,1453,1454,1459],{"__ignoreMap":68},[72,1455,1456],{"class":74,"line":75},[72,1457,1458],{},"toml = \"0.8\"          # TOML\n",[72,1460,1461],{"class":74,"line":86},[72,1462,1463],{},"serde_yaml = \"0.9\"    # YAML\n",[63,1465,1467],{"className":428,"code":1466,"language":430,"meta":68,"style":68},"let toml_str = toml::to_string(&u).unwrap();\nlet back: User = toml::from_str(&toml_str).unwrap();\n",[53,1468,1469,1501],{"__ignoreMap":68},[72,1470,1471,1473,1476,1478,1481,1483,1485,1487,1489,1491,1493,1495,1498],{"class":74,"line":75},[72,1472,1242],{"class":100},[72,1474,1475],{"class":508}," toml_str",[72,1477,611],{"class":100},[72,1479,1480],{"class":440}," toml",[72,1482,443],{"class":100},[72,1484,671],{"class":89},[72,1486,476],{"class":446},[72,1488,676],{"class":100},[72,1490,679],{"class":508},[72,1492,682],{"class":446},[72,1494,627],{"class":100},[72,1496,1497],{"class":89},"unwrap",[72,1499,1500],{"class":446},"();\n",[72,1502,1503,1505,1508,1510,1512,1514,1516,1518,1520,1522,1524,1527,1529,1531,1533],{"class":74,"line":86},[72,1504,1242],{"class":100},[72,1506,1507],{"class":508}," back",[72,1509,512],{"class":100},[72,1511,500],{"class":440},[72,1513,611],{"class":100},[72,1515,1480],{"class":440},[72,1517,443],{"class":100},[72,1519,815],{"class":89},[72,1521,476],{"class":446},[72,1523,676],{"class":100},[72,1525,1526],{"class":508},"toml_str",[72,1528,682],{"class":446},[72,1530,627],{"class":100},[72,1532,1497],{"class":89},[72,1534,1500],{"class":446},[15,1536,1537,1540],{},[19,1538,1539],{},"为什么是这个架构？"," serde 把「数据结构 ↔ 抽象数据模型」和「数据模型 ↔ 具体格式」拆开。你写的 derive 代码对 JSON\u002FTOML\u002FYAML 全部通用，社区也只维护一套宏——这就是 serde 能一统江湖的原因。",[24,1542],{},[27,1544,1546],{"id":1545},"第-2-章-anyhow-thiserror错误处理两件套","第 2 章 anyhow \u002F thiserror：错误处理两件套",[58,1548,1550],{"id":1549},"_21-它解决什么问题","2.1 它解决什么问题",[15,1552,1553,1554,1557,1558,1561],{},"Rust 没有异常，错误通过 ",[53,1555,1556],{},"Result\u003CT, E>"," 显式传递。问题是：一个函数里可能产生很多种错误（IO 错、解析错、网络错……），",[53,1559,1560],{},"E"," 该写什么类型？",[15,1563,1564],{},"社区共识：",[1566,1567,1568,1584],"table",{},[1569,1570,1571],"thead",{},[1572,1573,1574,1578,1581],"tr",{},[1575,1576,1577],"th",{},"场景",[1575,1579,1580],{},"用什么",[1575,1582,1583],{},"原因",[1585,1586,1587,1605],"tbody",{},[1572,1588,1589,1597,1602],{},[1590,1591,1592,1593,1596],"td",{},"写",[19,1594,1595],{},"应用","（bin、服务端）",[1590,1598,1599],{},[53,1600,1601],{},"anyhow",[1590,1603,1604],{},"调用者只关心「成功还是失败 + 发生了什么」，不需要细分错误类型",[1572,1606,1607,1613,1618],{},[1590,1608,1592,1609,1612],{},[19,1610,1611],{},"库","（给别人用的 crate）",[1590,1614,1615],{},[53,1616,1617],{},"thiserror",[1590,1619,1620],{},"库的调用者可能需要匹配具体错误做不同处理，错误类型必须是结构化的枚举",[58,1622,1624],{"id":1623},"_22-anyhow应用层的错误处理","2.2 anyhow：应用层的错误处理",[63,1626,1628],{"className":144,"code":1627,"language":146,"meta":68,"style":68},"[dependencies]\nanyhow = \"1\"\n",[53,1629,1630,1634],{"__ignoreMap":68},[72,1631,1632],{"class":74,"line":75},[72,1633,153],{},[72,1635,1636],{"class":74,"line":86},[72,1637,1638],{},"anyhow = \"1\"\n",[63,1640,1642],{"className":428,"code":1641,"language":430,"meta":68,"style":68},"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",[53,1643,1644,1675,1688,1692,1699,1734,1741,1748,1772,1809,1820,1824,1828,1855,1891,1906,1932,1937,1943,1947,1951,1967,1974,1996,2021,2026,2034,2067,2094,2130,2135,2142],{"__ignoreMap":68},[72,1645,1646,1648,1651,1653,1655,1658,1660,1663,1665,1668,1670,1673],{"class":74,"line":75},[72,1647,437],{"class":100},[72,1649,1650],{"class":440}," anyhow",[72,1652,443],{"class":100},[72,1654,447],{"class":446},[72,1656,1657],{"class":440},"Context",[72,1659,453],{"class":446},[72,1661,1662],{"class":440}," Result",[72,1664,453],{"class":446},[72,1666,1667],{"class":473}," bail",[72,1669,453],{"class":446},[72,1671,1672],{"class":473}," ensure",[72,1674,459],{"class":446},[72,1676,1677,1679,1681,1683,1686],{"class":74,"line":86},[72,1678,437],{"class":100},[72,1680,1121],{"class":440},[72,1682,443],{"class":100},[72,1684,1685],{"class":473},"fs",[72,1687,688],{"class":446},[72,1689,1690],{"class":74,"line":107},[72,1691,465],{"emptyLinePlaceholder":464},[72,1693,1694,1696],{"class":74,"line":122},[72,1695,999],{"class":78},[72,1697,1698],{"class":82}," 返回 anyhow::Result\u003CT>，内部是 Box\u003Cdyn Error>，任何错误都能用 ? 自动转换\n",[72,1700,1701,1703,1706,1708,1711,1713,1716,1719,1721,1723,1725,1727,1729,1732],{"class":74,"line":260},[72,1702,569],{"class":100},[72,1704,1705],{"class":89}," load_config",[72,1707,476],{"class":446},[72,1709,1710],{"class":508},"path",[72,1712,512],{"class":100},[72,1714,1715],{"class":100}," &",[72,1717,1718],{"class":440},"str",[72,1720,682],{"class":446},[72,1722,578],{"class":100},[72,1724,1662],{"class":440},[72,1726,543],{"class":446},[72,1728,546],{"class":440},[72,1730,1731],{"class":446},">",[72,1733,503],{"class":446},[72,1735,1736,1738],{"class":74,"line":274},[72,1737,596],{"class":78},[72,1739,1740],{"class":82}," Context 给底层错误附上业务语义。这是 anyhow 最有价值的功能：\n",[72,1742,1743,1745],{"class":74,"line":287},[72,1744,596],{"class":78},[72,1746,1747],{"class":82}," 没有它你只能看到 \"No such file or directory\"，不知道是哪个文件、在干嘛\n",[72,1749,1750,1752,1755,1757,1760,1762,1765,1767,1769],{"class":74,"line":301},[72,1751,605],{"class":100},[72,1753,1754],{"class":508}," content",[72,1756,611],{"class":100},[72,1758,1759],{"class":440}," fs",[72,1761,443],{"class":100},[72,1763,1764],{"class":89},"read_to_string",[72,1766,476],{"class":446},[72,1768,1710],{"class":508},[72,1770,1771],{"class":446},")\n",[72,1773,1774,1777,1780,1782,1785,1788,1790,1793,1795,1797,1799,1802,1805,1807],{"class":74,"line":317},[72,1775,1776],{"class":100},"        .",[72,1778,1779],{"class":89},"with_context",[72,1781,476],{"class":446},[72,1783,1784],{"class":100},"||",[72,1786,1787],{"class":89}," format!",[72,1789,476],{"class":446},[72,1791,1792],{"class":93},"\"读取配置文件 ",[72,1794,447],{"class":446},[72,1796,1710],{"class":93},[72,1798,707],{"class":446},[72,1800,1801],{"class":93}," 失败\"",[72,1803,1804],{"class":446},"))",[72,1806,685],{"class":100},[72,1808,688],{"class":446},[72,1810,1811,1813,1815,1818],{"class":74,"line":331},[72,1812,852],{"class":440},[72,1814,476],{"class":446},[72,1816,1817],{"class":508},"content",[72,1819,1771],{"class":446},[72,1821,1822],{"class":74,"line":353},[72,1823,560],{"class":446},[72,1825,1826],{"class":74,"line":602},[72,1827,465],{"emptyLinePlaceholder":464},[72,1829,1830,1832,1835,1837,1840,1842,1845,1847,1849,1851,1853],{"class":74,"line":657},[72,1831,569],{"class":100},[72,1833,1834],{"class":89}," check_age",[72,1836,476],{"class":446},[72,1838,1839],{"class":508},"age",[72,1841,512],{"class":100},[72,1843,1844],{"class":440}," i32",[72,1846,682],{"class":446},[72,1848,578],{"class":100},[72,1850,1662],{"class":440},[72,1852,589],{"class":446},[72,1854,503],{"class":446},[72,1856,1857,1860,1862,1864,1867,1870,1872,1875,1877,1879,1881,1883,1885,1888],{"class":74,"line":691},[72,1858,1859],{"class":89},"    ensure!",[72,1861,476],{"class":446},[72,1863,1839],{"class":508},[72,1865,1866],{"class":100}," >=",[72,1868,1869],{"class":89}," 0",[72,1871,453],{"class":446},[72,1873,1874],{"class":93}," \"年龄不能为负数: ",[72,1876,447],{"class":446},[72,1878,1839],{"class":93},[72,1880,707],{"class":446},[72,1882,699],{"class":93},[72,1884,712],{"class":446},[72,1886,1887],{"class":78},"   \u002F\u002F",[72,1889,1890],{"class":82}," 条件不满足则提前返回错误\n",[72,1892,1893,1896,1898,1901,1904],{"class":74,"line":720},[72,1894,1895],{"class":100},"    if",[72,1897,636],{"class":508},[72,1899,1900],{"class":100}," >",[72,1902,1903],{"class":89}," 150",[72,1905,503],{"class":446},[72,1907,1908,1911,1913,1916,1918,1920,1922,1924,1926,1929],{"class":74,"line":725},[72,1909,1910],{"class":89},"        bail!",[72,1912,476],{"class":446},[72,1914,1915],{"class":93},"\"年龄不合理: ",[72,1917,447],{"class":446},[72,1919,1839],{"class":93},[72,1921,707],{"class":446},[72,1923,699],{"class":93},[72,1925,712],{"class":446},[72,1927,1928],{"class":78},"              \u002F\u002F",[72,1930,1931],{"class":82}," 直接构造错误并返回，等价于 return Err(...)\n",[72,1933,1934],{"class":74,"line":733},[72,1935,1936],{"class":446},"    }\n",[72,1938,1939,1941],{"class":74,"line":769},[72,1940,852],{"class":440},[72,1942,855],{"class":446},[72,1944,1945],{"class":74,"line":774},[72,1946,560],{"class":446},[72,1948,1949],{"class":74,"line":782},[72,1950,465],{"emptyLinePlaceholder":464},[72,1952,1953,1955,1957,1959,1961,1963,1965],{"class":74,"line":797},[72,1954,569],{"class":100},[72,1956,572],{"class":89},[72,1958,575],{"class":446},[72,1960,578],{"class":100},[72,1962,1662],{"class":440},[72,1964,589],{"class":446},[72,1966,503],{"class":446},[72,1968,1969,1971],{"class":74,"line":829},[72,1970,596],{"class":78},[72,1972,1973],{"class":82}," main 也可以返回 Result，出错时自动打印错误并退出码非 0\n",[72,1975,1976,1978,1981,1983,1985,1987,1990,1992,1994],{"class":74,"line":849},[72,1977,605],{"class":100},[72,1979,1980],{"class":508}," cfg",[72,1982,611],{"class":100},[72,1984,1705],{"class":89},[72,1986,476],{"class":446},[72,1988,1989],{"class":93},"\"app.toml\"",[72,1991,682],{"class":446},[72,1993,685],{"class":100},[72,1995,688],{"class":446},[72,1997,1998,2000,2002,2005,2007,2009,2011,2013,2015,2018],{"class":74,"line":858},[72,1999,694],{"class":89},[72,2001,476],{"class":446},[72,2003,2004],{"class":93},"\"配置长度: ",[72,2006,742],{"class":446},[72,2008,699],{"class":93},[72,2010,453],{"class":446},[72,2012,1980],{"class":508},[72,2014,627],{"class":100},[72,2016,2017],{"class":89},"len",[72,2019,2020],{"class":446},"());\n",[72,2022,2024],{"class":74,"line":2023},25,[72,2025,465],{"emptyLinePlaceholder":464},[72,2027,2029,2031],{"class":74,"line":2028},26,[72,2030,596],{"class":78},[72,2032,2033],{"class":82}," 完整的错误链（含每一层 context）用 {:#} 打印\n",[72,2035,2037,2039,2041,2044,2046,2049,2051,2053,2055,2057,2060,2063,2065],{"class":74,"line":2036},27,[72,2038,1895],{"class":100},[72,2040,1299],{"class":100},[72,2042,2043],{"class":440}," Err",[72,2045,476],{"class":446},[72,2047,2048],{"class":508},"e",[72,2050,682],{"class":446},[72,2052,611],{"class":100},[72,2054,1834],{"class":89},[72,2056,476],{"class":446},[72,2058,2059],{"class":100},"-",[72,2061,2062],{"class":89},"1",[72,2064,682],{"class":446},[72,2066,503],{"class":446},[72,2068,2070,2073,2075,2078,2080,2083,2085,2087,2089,2092],{"class":74,"line":2069},28,[72,2071,2072],{"class":89},"        eprintln!",[72,2074,476],{"class":446},[72,2076,2077],{"class":93},"\"错误链: ",[72,2079,447],{"class":446},[72,2081,2082],{"class":93},":#",[72,2084,707],{"class":446},[72,2086,699],{"class":93},[72,2088,453],{"class":446},[72,2090,2091],{"class":508}," e",[72,2093,766],{"class":446},[72,2095,2097,2099,2101,2104,2106,2109,2111,2113,2115,2117,2119,2122,2125,2127],{"class":74,"line":2096},29,[72,2098,2072],{"class":89},[72,2100,476],{"class":446},[72,2102,2103],{"class":93},"\"根因: ",[72,2105,447],{"class":446},[72,2107,2108],{"class":93},":?",[72,2110,707],{"class":446},[72,2112,699],{"class":93},[72,2114,453],{"class":446},[72,2116,2091],{"class":508},[72,2118,627],{"class":100},[72,2120,2121],{"class":89},"root_cause",[72,2123,2124],{"class":446},"());",[72,2126,552],{"class":78},[72,2128,2129],{"class":82}," 最底层那个错误\n",[72,2131,2133],{"class":74,"line":2132},30,[72,2134,1936],{"class":446},[72,2136,2138,2140],{"class":74,"line":2137},31,[72,2139,852],{"class":440},[72,2141,855],{"class":446},[72,2143,2145],{"class":74,"line":2144},32,[72,2146,560],{"class":446},[15,2148,2149,2154,2155,2158],{},[19,2150,2151,2152,1194],{},"为什么要 ",[53,2153,1657],{}," 底层的 ",[53,2156,2157],{},"io::Error"," 只说「文件不存在」，但排查线上问题你需要知道「是在加载哪个配置时失败的」。Context 是在错误向上传播的路上不断追加业务上下文，形成错误链——这是用很低成本换来极好排障体验的做法。",[58,2160,2162],{"id":2161},"_23-thiserror给库定义错误类型","2.3 thiserror：给库定义错误类型",[63,2164,2166],{"className":144,"code":2165,"language":146,"meta":68,"style":68},"[dependencies]\nthiserror = \"2\"\n",[53,2167,2168,2172],{"__ignoreMap":68},[72,2169,2170],{"class":74,"line":75},[72,2171,153],{},[72,2173,2174],{"class":74,"line":86},[72,2175,2176],{},"thiserror = \"2\"\n",[63,2178,2180],{"className":428,"code":2179,"language":430,"meta":68,"style":68},"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",[53,2181,2182,2196,2200,2216,2229,2258,2271,2275,2313,2339,2343,2362,2377,2406,2410,2414,2448,2486,2502,2506,2510,2517,2528,2543,2583,2621,2631,2660,2664],{"__ignoreMap":68},[72,2183,2184,2186,2189,2191,2194],{"class":74,"line":75},[72,2185,437],{"class":100},[72,2187,2188],{"class":440}," thiserror",[72,2190,443],{"class":100},[72,2192,2193],{"class":440},"Error",[72,2195,688],{"class":446},[72,2197,2198],{"class":74,"line":86},[72,2199,465],{"emptyLinePlaceholder":464},[72,2201,2202,2204,2206,2208,2210,2212,2214],{"class":74,"line":107},[72,2203,470],{"class":446},[72,2205,177],{"class":473},[72,2207,476],{"class":446},[72,2209,2193],{"class":440},[72,2211,453],{"class":446},[72,2213,489],{"class":440},[72,2215,492],{"class":446},[72,2217,2218,2221,2224,2227],{"class":74,"line":122},[72,2219,2220],{"class":100},"pub",[72,2222,2223],{"class":100}," enum",[72,2225,2226],{"class":440}," StoreError",[72,2228,503],{"class":446},[72,2230,2231,2233,2236,2238,2241,2243,2245,2247,2250,2252,2255],{"class":74,"line":260},[72,2232,899],{"class":446},[72,2234,2235],{"class":473},"error",[72,2237,476],{"class":446},[72,2239,2240],{"class":93},"\"记录 ",[72,2242,447],{"class":446},[72,2244,1330],{"class":93},[72,2246,707],{"class":446},[72,2248,2249],{"class":93}," 不存在\"",[72,2251,916],{"class":446},[72,2253,2254],{"class":78},"                 \u002F\u002F",[72,2256,2257],{"class":82}," 错误信息模板，{0} 引用元组字段\n",[72,2259,2260,2263,2265,2268],{"class":74,"line":274},[72,2261,2262],{"class":89},"    NotFound",[72,2264,476],{"class":446},[72,2266,2267],{"class":440},"u64",[72,2269,2270],{"class":446},"),\n",[72,2272,2273],{"class":74,"line":287},[72,2274,465],{"emptyLinePlaceholder":464},[72,2276,2277,2279,2281,2283,2286,2288,2291,2293,2296,2298,2301,2303,2306,2308,2310],{"class":74,"line":301},[72,2278,899],{"class":446},[72,2280,2235],{"class":473},[72,2282,476],{"class":446},[72,2284,2285],{"class":93},"\"权限不足，需要 ",[72,2287,447],{"class":446},[72,2289,2290],{"class":93},"need",[72,2292,707],{"class":446},[72,2294,2295],{"class":93}," 级，当前 ",[72,2297,447],{"class":446},[72,2299,2300],{"class":93},"current",[72,2302,707],{"class":446},[72,2304,2305],{"class":93}," 级\"",[72,2307,916],{"class":446},[72,2309,552],{"class":78},[72,2311,2312],{"class":82}," 命名字段也可以\n",[72,2314,2315,2318,2320,2323,2325,2327,2329,2332,2334,2336],{"class":74,"line":317},[72,2316,2317],{"class":440},"    PermissionDenied",[72,2319,616],{"class":446},[72,2321,2322],{"class":508}," need",[72,2324,512],{"class":100},[72,2326,528],{"class":440},[72,2328,453],{"class":446},[72,2330,2331],{"class":508}," current",[72,2333,512],{"class":100},[72,2335,528],{"class":440},[72,2337,2338],{"class":446}," },\n",[72,2340,2341],{"class":74,"line":331},[72,2342,465],{"emptyLinePlaceholder":464},[72,2344,2345,2347,2349,2351,2354,2356,2359],{"class":74,"line":353},[72,2346,899],{"class":446},[72,2348,2235],{"class":473},[72,2350,476],{"class":446},[72,2352,2353],{"class":93},"\"数据库错误\"",[72,2355,916],{"class":446},[72,2357,2358],{"class":78},"                       \u002F\u002F",[72,2360,2361],{"class":82}," 展示给上层的文案\n",[72,2363,2364,2366,2369,2371,2374],{"class":74,"line":602},[72,2365,899],{"class":446},[72,2367,2368],{"class":473},"from",[72,2370,1338],{"class":446},[72,2372,2373],{"class":78},"                                     \u002F\u002F",[72,2375,2376],{"class":82}," 自动生成 From\u003Csqlx::Error>，? 可自动转换\n",[72,2378,2379,2382,2385,2388,2390,2393,2395,2397,2400,2403],{"class":74,"line":657},[72,2380,2381],{"class":89},"    Db",[72,2383,2384],{"class":446},"(#[",[72,2386,2387],{"class":473},"source",[72,2389,1338],{"class":446},[72,2391,2392],{"class":440}," sqlx",[72,2394,443],{"class":100},[72,2396,2193],{"class":440},[72,2398,2399],{"class":446},"),",[72,2401,2402],{"class":78},"                  \u002F\u002F",[72,2404,2405],{"class":82}," #[source] 保留底层错误供溯源\n",[72,2407,2408],{"class":74,"line":691},[72,2409,560],{"class":446},[72,2411,2412],{"class":74,"line":720},[72,2413,465],{"emptyLinePlaceholder":464},[72,2415,2416,2418,2421,2423,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446],{"class":74,"line":725},[72,2417,569],{"class":100},[72,2419,2420],{"class":89}," find_user",[72,2422,476],{"class":446},[72,2424,2425],{"class":508},"id",[72,2427,512],{"class":100},[72,2429,932],{"class":440},[72,2431,682],{"class":446},[72,2433,578],{"class":100},[72,2435,1662],{"class":440},[72,2437,543],{"class":446},[72,2439,546],{"class":440},[72,2441,453],{"class":446},[72,2443,2226],{"class":440},[72,2445,1731],{"class":446},[72,2447,503],{"class":446},[72,2449,2450,2452,2455,2458,2460,2462,2465,2467,2469,2472,2474,2477,2479,2481,2484],{"class":74,"line":733},[72,2451,1895],{"class":100},[72,2453,2454],{"class":508}," id",[72,2456,2457],{"class":100}," ==",[72,2459,1869],{"class":89},[72,2461,616],{"class":446},[72,2463,2464],{"class":100}," return",[72,2466,2043],{"class":440},[72,2468,476],{"class":446},[72,2470,2471],{"class":440},"StoreError",[72,2473,443],{"class":100},[72,2475,2476],{"class":89},"NotFound",[72,2478,476],{"class":446},[72,2480,2425],{"class":508},[72,2482,2483],{"class":446},"));",[72,2485,1181],{"class":446},[72,2487,2488,2490,2492,2495,2497,2499],{"class":74,"line":769},[72,2489,852],{"class":440},[72,2491,476],{"class":446},[72,2493,2494],{"class":93},"\"小明\"",[72,2496,627],{"class":100},[72,2498,630],{"class":89},[72,2500,2501],{"class":446},"())\n",[72,2503,2504],{"class":74,"line":774},[72,2505,560],{"class":446},[72,2507,2508],{"class":74,"line":782},[72,2509,465],{"emptyLinePlaceholder":464},[72,2511,2512,2514],{"class":74,"line":797},[72,2513,999],{"class":78},[72,2515,2516],{"class":82}," 调用方可以精确匹配\n",[72,2518,2519,2521,2524,2526],{"class":74,"line":829},[72,2520,569],{"class":100},[72,2522,2523],{"class":89}," handle",[72,2525,575],{"class":446},[72,2527,503],{"class":446},[72,2529,2530,2533,2535,2537,2539,2541],{"class":74,"line":849},[72,2531,2532],{"class":100},"    match",[72,2534,2420],{"class":89},[72,2536,476],{"class":446},[72,2538,1330],{"class":89},[72,2540,682],{"class":446},[72,2542,503],{"class":446},[72,2544,2545,2548,2550,2552,2554,2556,2558,2560,2562,2565,2568,2570,2573,2575,2577,2579,2581],{"class":74,"line":858},[72,2546,2547],{"class":440},"        Err",[72,2549,476],{"class":446},[72,2551,2471],{"class":440},[72,2553,443],{"class":100},[72,2555,2476],{"class":89},[72,2557,476],{"class":446},[72,2559,2425],{"class":508},[72,2561,1804],{"class":446},[72,2563,2564],{"class":100}," =>",[72,2566,2567],{"class":89}," println!",[72,2569,476],{"class":446},[72,2571,2572],{"class":93},"\"404: ",[72,2574,447],{"class":446},[72,2576,2425],{"class":93},[72,2578,707],{"class":446},[72,2580,699],{"class":93},[72,2582,2270],{"class":446},[72,2584,2585,2587,2589,2591,2593,2596,2598,2600,2602,2604,2606,2608,2611,2613,2615,2617,2619],{"class":74,"line":2023},[72,2586,2547],{"class":440},[72,2588,476],{"class":446},[72,2590,2471],{"class":440},[72,2592,443],{"class":100},[72,2594,2595],{"class":89},"Db",[72,2597,476],{"class":446},[72,2599,2048],{"class":508},[72,2601,1804],{"class":446},[72,2603,2564],{"class":100},[72,2605,2567],{"class":89},[72,2607,476],{"class":446},[72,2609,2610],{"class":93},"\"数据库挂了: ",[72,2612,447],{"class":446},[72,2614,2048],{"class":93},[72,2616,707],{"class":446},[72,2618,699],{"class":93},[72,2620,2270],{"class":446},[72,2622,2623,2626,2628],{"class":74,"line":2028},[72,2624,2625],{"class":508},"        _",[72,2627,2564],{"class":100},[72,2629,2630],{"class":446}," {}\n",[72,2632,2633,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658],{"class":74,"line":2036},[72,2634,2635],{"class":440},"        Ok",[72,2637,476],{"class":446},[72,2639,1307],{"class":508},[72,2641,682],{"class":446},[72,2643,2564],{"class":100},[72,2645,2567],{"class":89},[72,2647,476],{"class":446},[72,2649,699],{"class":93},[72,2651,447],{"class":446},[72,2653,1307],{"class":93},[72,2655,707],{"class":446},[72,2657,699],{"class":93},[72,2659,2270],{"class":446},[72,2661,2662],{"class":74,"line":2069},[72,2663,1936],{"class":446},[72,2665,2666],{"class":74,"line":2096},[72,2667,560],{"class":446},[15,2669,2670,2673,2674,2677,2678,2681,2682,2684,2685,2688,2689,2688,2692,2695],{},[19,2671,2672],{},"为什么库不用 anyhow？"," 如果你的库返回 ",[53,2675,2676],{},"anyhow::Error","，调用方就没法 ",[53,2679,2680],{},"match"," 具体错误种类（anyhow 内部是类型擦除的），只能靠字符串匹配——这对库消费者来说是不可接受的。",[53,2683,1617],{}," 只是帮你少写 ",[53,2686,2687],{},"impl Display"," \u002F ",[53,2690,2691],{},"impl Error",[53,2693,2694],{},"impl From"," 的样板代码，产出的是完全标准、类型安全的错误枚举。",[12,2697,2698],{},[15,2699,2700,2701,2703,2704,2706],{},"补充：应用层也可以用「",[53,2702,1617],{}," 定义领域错误 + ",[53,2705,1601],{}," 做顶层汇总」的混合模式，二者不冲突。",[24,2708],{},[27,2710,2712],{"id":2711},"第-3-章-tokio异步运行时","第 3 章 tokio：异步运行时",[58,2714,2716],{"id":2715},"_31-它解决什么问题","3.1 它解决什么问题",[15,2718,2719,2720,2723,2724,2727,2728,2731,2732,2735],{},"写网络服务时，一个线程阻塞等 IO 是巨大浪费。Rust 的 ",[53,2721,2722],{},"async\u002Fawait"," 只是",[19,2725,2726],{},"语法","，",[53,2729,2730],{},"async fn"," 本身不会运行——必须有一个",[19,2733,2734],{},"运行时","来轮询、调度这些 future。tokio 是事实标准的异步运行时（带工作窃取调度器、定时器、异步 IO、异步同步原语）。",[15,2737,2738,2741],{},[19,2739,2740],{},"为什么 Rust 异步要显式选运行时？"," 因为嵌入式、浏览器 WASM、服务端对调度和 IO 的需求完全不同，标准库不替你决定。代价是入门多一步，收益是各场景都能用最优实现。",[63,2743,2745],{"className":144,"code":2744,"language":146,"meta":68,"style":68},"[dependencies]\ntokio = { version = \"1\", features = [\"full\"] } # 学习中直接 full，生产再裁剪\n",[53,2746,2747,2751],{"__ignoreMap":68},[72,2748,2749],{"class":74,"line":75},[72,2750,153],{},[72,2752,2753],{"class":74,"line":86},[72,2754,2755],{},"tokio = { version = \"1\", features = [\"full\"] } # 学习中直接 full，生产再裁剪\n",[58,2757,2759],{"id":2758},"_32-入口与-spawn","3.2 入口与 spawn",[63,2761,2763],{"className":428,"code":2762,"language":430,"meta":68,"style":68},"\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",[53,2764,2765,2772,2787,2801,2822,2830,2837,2882,2887,2892,2896,2903,2921,2940],{"__ignoreMap":68},[72,2766,2767,2769],{"class":74,"line":75},[72,2768,999],{"class":78},[72,2770,2771],{"class":82}," #[tokio::main] 宏把 main 变成运行时入口，等价于手动 Builder::new_multi_thread()...block_on\n",[72,2773,2774,2776,2779,2781,2784],{"class":74,"line":86},[72,2775,470],{"class":446},[72,2777,2778],{"class":473},"tokio",[72,2780,443],{"class":100},[72,2782,2783],{"class":473},"main",[72,2785,2786],{"class":446},"]\n",[72,2788,2789,2792,2795,2797,2799],{"class":74,"line":107},[72,2790,2791],{"class":100},"async",[72,2793,2794],{"class":100}," fn",[72,2796,572],{"class":89},[72,2798,575],{"class":446},[72,2800,503],{"class":446},[72,2802,2803,2805,2807,2809,2811,2813,2816,2818,2820],{"class":74,"line":122},[72,2804,605],{"class":100},[72,2806,2523],{"class":508},[72,2808,611],{"class":100},[72,2810,114],{"class":440},[72,2812,443],{"class":100},[72,2814,2815],{"class":89},"spawn",[72,2817,476],{"class":446},[72,2819,2791],{"class":100},[72,2821,503],{"class":446},[72,2823,2824,2827],{"class":74,"line":260},[72,2825,2826],{"class":78},"        \u002F\u002F",[72,2828,2829],{"class":82}," spawn 创建一个新的异步任务，由运行时并发调度\n",[72,2831,2832,2834],{"class":74,"line":274},[72,2833,2826],{"class":78},[72,2835,2836],{"class":82}," 类似线程，但成本极低：可以轻松起十万级任务\n",[72,2838,2839,2842,2844,2847,2849,2852,2854,2857,2859,2861,2863,2866,2868,2871,2873,2875,2877,2880],{"class":74,"line":287},[72,2840,2841],{"class":440},"        tokio",[72,2843,443],{"class":100},[72,2845,2846],{"class":440},"time",[72,2848,443],{"class":100},[72,2850,2851],{"class":89},"sleep",[72,2853,476],{"class":446},[72,2855,2856],{"class":440},"std",[72,2858,443],{"class":100},[72,2860,2846],{"class":440},[72,2862,443],{"class":100},[72,2864,2865],{"class":440},"Duration",[72,2867,443],{"class":100},[72,2869,2870],{"class":89},"from_secs",[72,2872,476],{"class":446},[72,2874,2062],{"class":89},[72,2876,1804],{"class":446},[72,2878,2879],{"class":100},".await",[72,2881,688],{"class":446},[72,2883,2884],{"class":74,"line":301},[72,2885,2886],{"class":89},"        42\n",[72,2888,2889],{"class":74,"line":317},[72,2890,2891],{"class":446},"    });\n",[72,2893,2894],{"class":74,"line":331},[72,2895,465],{"emptyLinePlaceholder":464},[72,2897,2898,2900],{"class":74,"line":353},[72,2899,596],{"class":78},[72,2901,2902],{"class":82}," JoinHandle 类似线程的 join，await 拿到任务返回值\n",[72,2904,2905,2907,2910,2912,2914,2917,2919],{"class":74,"line":602},[72,2906,605],{"class":100},[72,2908,2909],{"class":508}," answer",[72,2911,611],{"class":100},[72,2913,2523],{"class":508},[72,2915,2916],{"class":100},".await.",[72,2918,1497],{"class":89},[72,2920,1500],{"class":446},[72,2922,2923,2925,2927,2929,2931,2934,2936,2938],{"class":74,"line":657},[72,2924,694],{"class":89},[72,2926,476],{"class":446},[72,2928,699],{"class":93},[72,2930,447],{"class":446},[72,2932,2933],{"class":93},"answer",[72,2935,707],{"class":446},[72,2937,699],{"class":93},[72,2939,766],{"class":446},[72,2941,2942],{"class":74,"line":691},[72,2943,560],{"class":446},[58,2945,2947],{"id":2946},"_33-并发等待join-try_join-select","3.3 并发等待：join! \u002F try_join! \u002F select!",[63,2949,2951],{"className":428,"code":2950,"language":430,"meta":68,"style":68},"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",[53,2952,2953,2976,2980,3048,3122,3126,3138,3150,3157,3193,3197,3204,3254,3258,3265,3277,3307,3341,3345,3368],{"__ignoreMap":68},[72,2954,2955,2957,2959,2961,2963,2965,2967,2969,2971,2974],{"class":74,"line":75},[72,2956,437],{"class":100},[72,2958,114],{"class":440},[72,2960,443],{"class":100},[72,2962,2846],{"class":440},[72,2964,443],{"class":100},[72,2966,447],{"class":446},[72,2968,2851],{"class":473},[72,2970,453],{"class":446},[72,2972,2973],{"class":440}," Duration",[72,2975,459],{"class":446},[72,2977,2978],{"class":74,"line":86},[72,2979,465],{"emptyLinePlaceholder":464},[72,2981,2982,2984,2986,2989,2991,2993,2995,2997,2999,3001,3003,3005,3007,3010,3012,3014,3016,3019,3021,3024,3026,3028,3031,3034,3036,3039,3041,3043,3046],{"class":74,"line":107},[72,2983,2791],{"class":100},[72,2985,2794],{"class":100},[72,2987,2988],{"class":89}," fetch_user",[72,2990,575],{"class":446},[72,2992,578],{"class":100},[72,2994,1662],{"class":440},[72,2996,543],{"class":446},[72,2998,546],{"class":440},[72,3000,453],{"class":446},[72,3002,515],{"class":440},[72,3004,1731],{"class":446},[72,3006,616],{"class":446},[72,3008,3009],{"class":89}," sleep",[72,3011,476],{"class":446},[72,3013,2865],{"class":440},[72,3015,443],{"class":100},[72,3017,3018],{"class":89},"from_millis",[72,3020,476],{"class":446},[72,3022,3023],{"class":89},"300",[72,3025,1804],{"class":446},[72,3027,2879],{"class":100},[72,3029,3030],{"class":446},";",[72,3032,3033],{"class":440}," Ok",[72,3035,476],{"class":446},[72,3037,3038],{"class":93},"\"user\"",[72,3040,627],{"class":100},[72,3042,630],{"class":89},[72,3044,3045],{"class":446},"())",[72,3047,1181],{"class":446},[72,3049,3050,3052,3054,3057,3059,3061,3063,3065,3068,3070,3072,3074,3076,3078,3080,3082,3084,3086,3088,3090,3092,3095,3097,3099,3101,3103,3105,3108,3110,3113,3115,3117,3120],{"class":74,"line":122},[72,3051,2791],{"class":100},[72,3053,2794],{"class":100},[72,3055,3056],{"class":89}," fetch_orders",[72,3058,575],{"class":446},[72,3060,578],{"class":100},[72,3062,1662],{"class":440},[72,3064,543],{"class":446},[72,3066,3067],{"class":440},"Vec",[72,3069,543],{"class":446},[72,3071,546],{"class":440},[72,3073,549],{"class":446},[72,3075,515],{"class":440},[72,3077,1731],{"class":446},[72,3079,616],{"class":446},[72,3081,3009],{"class":89},[72,3083,476],{"class":446},[72,3085,2865],{"class":440},[72,3087,443],{"class":100},[72,3089,3018],{"class":89},[72,3091,476],{"class":446},[72,3093,3094],{"class":89},"500",[72,3096,1804],{"class":446},[72,3098,2879],{"class":100},[72,3100,3030],{"class":446},[72,3102,3033],{"class":440},[72,3104,476],{"class":446},[72,3106,3107],{"class":89},"vec!",[72,3109,1316],{"class":446},[72,3111,3112],{"class":93},"\"o1\"",[72,3114,627],{"class":100},[72,3116,630],{"class":89},[72,3118,3119],{"class":446},"()])",[72,3121,1181],{"class":446},[72,3123,3124],{"class":74,"line":260},[72,3125,465],{"emptyLinePlaceholder":464},[72,3127,3128,3130,3132,3134,3136],{"class":74,"line":274},[72,3129,470],{"class":446},[72,3131,2778],{"class":473},[72,3133,443],{"class":100},[72,3135,2783],{"class":473},[72,3137,2786],{"class":446},[72,3139,3140,3142,3144,3146,3148],{"class":74,"line":287},[72,3141,2791],{"class":100},[72,3143,2794],{"class":100},[72,3145,572],{"class":89},[72,3147,575],{"class":446},[72,3149,503],{"class":446},[72,3151,3152,3154],{"class":74,"line":301},[72,3153,596],{"class":78},[72,3155,3156],{"class":82}," join!: 并发执行，全部完成后一起拿结果。总耗时取最长的那个（500ms 而非 800ms）\n",[72,3158,3159,3161,3164,3166,3168,3171,3173,3175,3177,3179,3182,3184,3187,3189,3191],{"class":74,"line":317},[72,3160,605],{"class":100},[72,3162,3163],{"class":446}," (",[72,3165,679],{"class":508},[72,3167,453],{"class":446},[72,3169,3170],{"class":508}," o",[72,3172,682],{"class":446},[72,3174,611],{"class":100},[72,3176,114],{"class":440},[72,3178,443],{"class":100},[72,3180,3181],{"class":89},"join!",[72,3183,476],{"class":446},[72,3185,3186],{"class":89},"fetch_user",[72,3188,633],{"class":446},[72,3190,3056],{"class":89},[72,3192,2020],{"class":446},[72,3194,3195],{"class":74,"line":331},[72,3196,465],{"emptyLinePlaceholder":464},[72,3198,3199,3201],{"class":74,"line":353},[72,3200,596],{"class":78},[72,3202,3203],{"class":82}," try_join!: 任何一个出错立即返回 Err，其余取消。适合「缺一不可」的聚合请求\n",[72,3205,3206,3208,3211,3213,3215,3218,3220,3222,3224,3226,3228,3231,3233,3235,3237,3239,3241,3244,3246,3248,3250,3252],{"class":74,"line":602},[72,3207,605],{"class":100},[72,3209,3210],{"class":508}," r",[72,3212,512],{"class":100},[72,3214,1662],{"class":440},[72,3216,3217],{"class":446},"\u003C(",[72,3219,546],{"class":440},[72,3221,453],{"class":446},[72,3223,967],{"class":440},[72,3225,543],{"class":446},[72,3227,546],{"class":440},[72,3229,3230],{"class":446},">),",[72,3232,515],{"class":440},[72,3234,1731],{"class":446},[72,3236,611],{"class":100},[72,3238,114],{"class":440},[72,3240,443],{"class":100},[72,3242,3243],{"class":89},"try_join!",[72,3245,476],{"class":446},[72,3247,3186],{"class":89},[72,3249,633],{"class":446},[72,3251,3056],{"class":89},[72,3253,2020],{"class":446},[72,3255,3256],{"class":74,"line":657},[72,3257,465],{"emptyLinePlaceholder":464},[72,3259,3260,3262],{"class":74,"line":691},[72,3261,596],{"class":78},[72,3263,3264],{"class":82}," select!: 谁先完成用谁，其余被 drop。适合超时、竞速备份请求\n",[72,3266,3267,3270,3272,3275],{"class":74,"line":720},[72,3268,3269],{"class":440},"    tokio",[72,3271,443],{"class":100},[72,3273,3274],{"class":89},"select!",[72,3276,503],{"class":446},[72,3278,3279,3282,3284,3286,3288,3290,3292,3294,3297,3299,3301,3303,3305],{"class":74,"line":725},[72,3280,3281],{"class":508},"        v",[72,3283,611],{"class":100},[72,3285,2988],{"class":89},[72,3287,575],{"class":446},[72,3289,2564],{"class":100},[72,3291,2567],{"class":89},[72,3293,476],{"class":446},[72,3295,3296],{"class":93},"\"user 先到: ",[72,3298,447],{"class":446},[72,3300,840],{"class":93},[72,3302,707],{"class":446},[72,3304,699],{"class":93},[72,3306,2270],{"class":446},[72,3308,3309,3311,3313,3315,3317,3319,3321,3323,3325,3328,3330,3332,3334,3336,3339],{"class":74,"line":733},[72,3310,2625],{"class":508},[72,3312,611],{"class":100},[72,3314,3009],{"class":89},[72,3316,476],{"class":446},[72,3318,2865],{"class":440},[72,3320,443],{"class":100},[72,3322,2870],{"class":89},[72,3324,476],{"class":446},[72,3326,3327],{"class":89},"10",[72,3329,1804],{"class":446},[72,3331,2564],{"class":100},[72,3333,2567],{"class":89},[72,3335,476],{"class":446},[72,3337,3338],{"class":93},"\"超时了\"",[72,3340,2270],{"class":446},[72,3342,3343],{"class":74,"line":769},[72,3344,1936],{"class":446},[72,3346,3347,3349,3352,3354,3356,3358,3360,3362,3364,3366],{"class":74,"line":774},[72,3348,605],{"class":100},[72,3350,3351],{"class":508}," _",[72,3353,611],{"class":100},[72,3355,3163],{"class":446},[72,3357,679],{"class":508},[72,3359,453],{"class":446},[72,3361,3170],{"class":508},[72,3363,453],{"class":446},[72,3365,3210],{"class":508},[72,3367,766],{"class":446},[72,3369,3370],{"class":74,"line":782},[72,3371,560],{"class":446},[15,3373,3374,3381,3382,3384,3385,376],{},[19,3375,2151,3376,3378,3379,1194],{},[53,3377,3181],{}," 而不是依次 ",[53,3380,2879],{}," 依次 await 是串行：等 user 回来再发 orders 请求，总延迟翻倍。",[53,3383,3181],{}," 让多个 IO 同时在飞，这是异步最大的收益场景之一——",[19,3386,3387],{},"IO 聚合并发",[58,3389,3391],{"id":3390},"_34-超时与取消","3.4 超时与取消",[63,3393,3395],{"className":428,"code":3394,"language":430,"meta":68,"style":68},"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",[53,3396,3397,3420,3424,3484,3488,3500,3512,3519,3548,3577,3604,3608],{"__ignoreMap":68},[72,3398,3399,3401,3403,3405,3407,3409,3411,3414,3416,3418],{"class":74,"line":75},[72,3400,437],{"class":100},[72,3402,114],{"class":440},[72,3404,443],{"class":100},[72,3406,2846],{"class":440},[72,3408,443],{"class":100},[72,3410,447],{"class":446},[72,3412,3413],{"class":473},"timeout",[72,3415,453],{"class":446},[72,3417,2973],{"class":440},[72,3419,459],{"class":446},[72,3421,3422],{"class":74,"line":86},[72,3423,465],{"emptyLinePlaceholder":464},[72,3425,3426,3428,3430,3433,3435,3437,3439,3442,3445,3448,3450,3452,3454,3456,3458,3460,3462,3464,3466,3468,3470,3473,3475,3477,3479,3482],{"class":74,"line":107},[72,3427,2791],{"class":100},[72,3429,2794],{"class":100},[72,3431,3432],{"class":89}," slow",[72,3434,575],{"class":446},[72,3436,578],{"class":100},[72,3438,1715],{"class":100},[72,3440,3441],{"class":446},"'",[72,3443,3444],{"class":440},"static",[72,3446,3447],{"class":440}," str",[72,3449,616],{"class":446},[72,3451,114],{"class":440},[72,3453,443],{"class":100},[72,3455,2846],{"class":440},[72,3457,443],{"class":100},[72,3459,2851],{"class":89},[72,3461,476],{"class":446},[72,3463,2865],{"class":440},[72,3465,443],{"class":100},[72,3467,2870],{"class":89},[72,3469,476],{"class":446},[72,3471,3472],{"class":89},"5",[72,3474,1804],{"class":446},[72,3476,2879],{"class":100},[72,3478,3030],{"class":446},[72,3480,3481],{"class":93}," \"done\"",[72,3483,1181],{"class":446},[72,3485,3486],{"class":74,"line":122},[72,3487,465],{"emptyLinePlaceholder":464},[72,3489,3490,3492,3494,3496,3498],{"class":74,"line":260},[72,3491,470],{"class":446},[72,3493,2778],{"class":473},[72,3495,443],{"class":100},[72,3497,2783],{"class":473},[72,3499,2786],{"class":446},[72,3501,3502,3504,3506,3508,3510],{"class":74,"line":274},[72,3503,2791],{"class":100},[72,3505,2794],{"class":100},[72,3507,572],{"class":89},[72,3509,575],{"class":446},[72,3511,503],{"class":446},[72,3513,3514,3516],{"class":74,"line":287},[72,3515,596],{"class":78},[72,3517,3518],{"class":82}," timeout 是 select! 的常见封装：到点未完成就返回 Err\n",[72,3520,3521,3523,3526,3528,3530,3532,3534,3536,3538,3540,3542,3544,3546],{"class":74,"line":301},[72,3522,2532],{"class":100},[72,3524,3525],{"class":89}," timeout",[72,3527,476],{"class":446},[72,3529,2865],{"class":440},[72,3531,443],{"class":100},[72,3533,2870],{"class":89},[72,3535,476],{"class":446},[72,3537,2062],{"class":89},[72,3539,2399],{"class":446},[72,3541,3432],{"class":89},[72,3543,3045],{"class":446},[72,3545,2879],{"class":100},[72,3547,503],{"class":446},[72,3549,3550,3552,3554,3557,3559,3561,3563,3565,3567,3569,3571,3573,3575],{"class":74,"line":317},[72,3551,2635],{"class":440},[72,3553,476],{"class":446},[72,3555,3556],{"class":508},"v",[72,3558,682],{"class":446},[72,3560,2564],{"class":100},[72,3562,2567],{"class":89},[72,3564,476],{"class":446},[72,3566,699],{"class":93},[72,3568,447],{"class":446},[72,3570,3556],{"class":93},[72,3572,707],{"class":446},[72,3574,699],{"class":93},[72,3576,2270],{"class":446},[72,3578,3579,3581,3583,3586,3588,3590,3592,3594,3597,3599,3601],{"class":74,"line":331},[72,3580,2547],{"class":440},[72,3582,476],{"class":446},[72,3584,3585],{"class":508},"_",[72,3587,682],{"class":446},[72,3589,2564],{"class":100},[72,3591,2567],{"class":89},[72,3593,476],{"class":446},[72,3595,3596],{"class":93},"\"超时取消\"",[72,3598,2399],{"class":446},[72,3600,552],{"class":78},[72,3602,3603],{"class":82}," 超时时 future 被 drop，任务自然取消\n",[72,3605,3606],{"class":74,"line":353},[72,3607,1936],{"class":446},[72,3609,3610],{"class":74,"line":602},[72,3611,560],{"class":446},[15,3613,3614,3617],{},[19,3615,3616],{},"为什么 drop 就能取消？"," Rust 的 future 是惰性状态机，不轮询就不前进。drop 掉 future = 销毁状态机 = 取消。没有线程中断那样的危险操作，这就是 Rust 异步取消模型安全优雅的原因。代价是：被 drop 点必须是不变量的安全点（tokio 的锁等原语都保证了这点）。",[58,3619,3621],{"id":3620},"_35-异步文件与进程","3.5 异步文件与进程",[63,3623,3625],{"className":428,"code":3624,"language":430,"meta":68,"style":68},"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",[53,3626,3627,3639,3664,3668,3680,3706,3713,3740,3764,3768,3775,3810,3841,3856,3860,3880,3917,3923],{"__ignoreMap":68},[72,3628,3629,3631,3633,3635,3637],{"class":74,"line":75},[72,3630,437],{"class":100},[72,3632,114],{"class":440},[72,3634,443],{"class":100},[72,3636,1685],{"class":473},[72,3638,688],{"class":446},[72,3640,3641,3643,3645,3647,3650,3652,3654,3657,3659,3662],{"class":74,"line":86},[72,3642,437],{"class":100},[72,3644,114],{"class":440},[72,3646,443],{"class":100},[72,3648,3649],{"class":440},"io",[72,3651,443],{"class":100},[72,3653,447],{"class":446},[72,3655,3656],{"class":440},"AsyncWriteExt",[72,3658,453],{"class":446},[72,3660,3661],{"class":440}," AsyncReadExt",[72,3663,459],{"class":446},[72,3665,3666],{"class":74,"line":107},[72,3667,465],{"emptyLinePlaceholder":464},[72,3669,3670,3672,3674,3676,3678],{"class":74,"line":122},[72,3671,470],{"class":446},[72,3673,2778],{"class":473},[72,3675,443],{"class":100},[72,3677,2783],{"class":473},[72,3679,2786],{"class":446},[72,3681,3682,3684,3686,3688,3690,3692,3694,3696,3698,3700,3702,3704],{"class":74,"line":260},[72,3683,2791],{"class":100},[72,3685,2794],{"class":100},[72,3687,572],{"class":89},[72,3689,575],{"class":446},[72,3691,578],{"class":100},[72,3693,1121],{"class":440},[72,3695,443],{"class":100},[72,3697,3649],{"class":440},[72,3699,443],{"class":100},[72,3701,586],{"class":440},[72,3703,589],{"class":446},[72,3705,503],{"class":446},[72,3707,3708,3710],{"class":74,"line":274},[72,3709,596],{"class":78},[72,3711,3712],{"class":82}," 高层 API：一次性读写\n",[72,3714,3715,3718,3720,3723,3725,3728,3730,3733,3735,3738],{"class":74,"line":287},[72,3716,3717],{"class":440},"    fs",[72,3719,443],{"class":100},[72,3721,3722],{"class":89},"write",[72,3724,476],{"class":446},[72,3726,3727],{"class":93},"\"a.txt\"",[72,3729,453],{"class":446},[72,3731,3732],{"class":93}," \"你好\"",[72,3734,682],{"class":446},[72,3736,3737],{"class":100},".await?",[72,3739,688],{"class":446},[72,3741,3742,3744,3746,3748,3750,3752,3754,3756,3758,3760,3762],{"class":74,"line":301},[72,3743,605],{"class":100},[72,3745,662],{"class":508},[72,3747,611],{"class":100},[72,3749,1759],{"class":440},[72,3751,443],{"class":100},[72,3753,1764],{"class":89},[72,3755,476],{"class":446},[72,3757,3727],{"class":93},[72,3759,682],{"class":446},[72,3761,3737],{"class":100},[72,3763,688],{"class":446},[72,3765,3766],{"class":74,"line":317},[72,3767,465],{"emptyLinePlaceholder":464},[72,3769,3770,3772],{"class":74,"line":331},[72,3771,596],{"class":78},[72,3773,3774],{"class":82}," 低层 API：流式处理大文件\n",[72,3776,3777,3779,3782,3785,3787,3789,3791,3794,3796,3799,3801,3804,3806,3808],{"class":74,"line":353},[72,3778,605],{"class":100},[72,3780,3781],{"class":100}," mut",[72,3783,3784],{"class":508}," f",[72,3786,611],{"class":100},[72,3788,1759],{"class":440},[72,3790,443],{"class":100},[72,3792,3793],{"class":440},"File",[72,3795,443],{"class":100},[72,3797,3798],{"class":89},"create",[72,3800,476],{"class":446},[72,3802,3803],{"class":93},"\"b.txt\"",[72,3805,682],{"class":446},[72,3807,3737],{"class":100},[72,3809,688],{"class":446},[72,3811,3812,3815,3817,3820,3822,3824,3826,3829,3831,3833,3835,3838],{"class":74,"line":602},[72,3813,3814],{"class":508},"    f",[72,3816,627],{"class":100},[72,3818,3819],{"class":89},"write_all",[72,3821,476],{"class":446},[72,3823,704],{"class":508},[72,3825,627],{"class":100},[72,3827,3828],{"class":89},"as_bytes",[72,3830,3045],{"class":446},[72,3832,3737],{"class":100},[72,3834,3030],{"class":446},[72,3836,3837],{"class":78},"  \u002F\u002F",[72,3839,3840],{"class":82}," 注意 trait 方法要 use AsyncWriteExt\n",[72,3842,3843,3845,3847,3850,3852,3854],{"class":74,"line":657},[72,3844,3814],{"class":508},[72,3846,627],{"class":100},[72,3848,3849],{"class":89},"flush",[72,3851,575],{"class":446},[72,3853,3737],{"class":100},[72,3855,688],{"class":446},[72,3857,3858],{"class":74,"line":691},[72,3859,465],{"emptyLinePlaceholder":464},[72,3861,3862,3864,3866,3869,3871,3873,3875,3878],{"class":74,"line":720},[72,3863,605],{"class":100},[72,3865,3781],{"class":100},[72,3867,3868],{"class":508}," buf",[72,3870,611],{"class":100},[72,3872,515],{"class":440},[72,3874,443],{"class":100},[72,3876,3877],{"class":89},"new",[72,3879,1500],{"class":446},[72,3881,3882,3884,3886,3888,3890,3893,3895,3897,3899,3902,3904,3906,3909,3911,3913,3915],{"class":74,"line":725},[72,3883,3717],{"class":440},[72,3885,443],{"class":100},[72,3887,3793],{"class":440},[72,3889,443],{"class":100},[72,3891,3892],{"class":89},"open",[72,3894,476],{"class":446},[72,3896,3803],{"class":93},[72,3898,682],{"class":446},[72,3900,3901],{"class":100},".await?.",[72,3903,1764],{"class":89},[72,3905,476],{"class":446},[72,3907,3908],{"class":100},"&mut",[72,3910,3868],{"class":508},[72,3912,682],{"class":446},[72,3914,3737],{"class":100},[72,3916,688],{"class":446},[72,3918,3919,3921],{"class":74,"line":733},[72,3920,852],{"class":440},[72,3922,855],{"class":446},[72,3924,3925],{"class":74,"line":769},[72,3926,560],{"class":446},[12,3928,3929],{},[15,3930,3931,3932,141],{},"注意：tokio 的异步文件 IO 底层其实是线程池（操作系统对普通文件没有真正的异步接口）。",[19,3933,3934,3935],{},"CPU 密集或必须用阻塞 API 的活，用 ",[53,3936,3937],{},"tokio::task::spawn_blocking",[63,3939,3941],{"className":428,"code":3940,"language":430,"meta":68,"style":68},"let hash = tokio::task::spawn_blocking(|| {\n    \u002F\u002F 这里跑同步的重计算，不会卡住异步调度器的工作线程\n    expensive_hash()\n}).await.unwrap();\n",[53,3942,3943,3970,3977,3985],{"__ignoreMap":68},[72,3944,3945,3947,3950,3952,3954,3956,3959,3961,3964,3966,3968],{"class":74,"line":75},[72,3946,1242],{"class":100},[72,3948,3949],{"class":508}," hash",[72,3951,611],{"class":100},[72,3953,114],{"class":440},[72,3955,443],{"class":100},[72,3957,3958],{"class":440},"task",[72,3960,443],{"class":100},[72,3962,3963],{"class":89},"spawn_blocking",[72,3965,476],{"class":446},[72,3967,1784],{"class":100},[72,3969,503],{"class":446},[72,3971,3972,3974],{"class":74,"line":86},[72,3973,596],{"class":78},[72,3975,3976],{"class":82}," 这里跑同步的重计算，不会卡住异步调度器的工作线程\n",[72,3978,3979,3982],{"class":74,"line":107},[72,3980,3981],{"class":89},"    expensive_hash",[72,3983,3984],{"class":446},"()\n",[72,3986,3987,3990,3992,3994],{"class":74,"line":122},[72,3988,3989],{"class":446},"})",[72,3991,2916],{"class":100},[72,3993,1497],{"class":89},[72,3995,1500],{"class":446},[15,3997,3998,4001,4002,4005,4006,376],{},[19,3999,4000],{},"为什么阻塞代码会卡住整个程序？"," tokio 工作线程数量有限（默认 = CPU 核数）。一个任务在工作线程里做阻塞调用（",[53,4003,4004],{},"std::thread::sleep","、同步文件 IO、重计算），这个线程就停摆了，它手上其他几百个异步任务全部挨饿。所以铁律：",[19,4007,4008],{},"async 代码里绝不调用阻塞 API",[58,4010,4012],{"id":4011},"_36-异步同步原语","3.6 异步同步原语",[63,4014,4016],{"className":428,"code":4015,"language":430,"meta":68,"style":68},"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",[53,4017,4018,4036,4080,4084,4096,4108,4115,4148,4165,4184,4203,4230,4260,4276,4281,4285,4309,4344,4348,4355,4401,4417,4436,4473,4478,4482,4518,4538,4542,4546,4553,4586,4625,4648,4653,4661,4697,4722,4740,4762,4786],{"__ignoreMap":68},[72,4019,4020,4022,4024,4026,4029,4031,4034],{"class":74,"line":75},[72,4021,437],{"class":100},[72,4023,1121],{"class":440},[72,4025,443],{"class":100},[72,4027,4028],{"class":440},"sync",[72,4030,443],{"class":100},[72,4032,4033],{"class":440},"Arc",[72,4035,688],{"class":446},[72,4037,4038,4040,4042,4044,4046,4048,4050,4053,4055,4058,4060,4063,4065,4068,4070,4073,4075,4078],{"class":74,"line":86},[72,4039,437],{"class":100},[72,4041,114],{"class":440},[72,4043,443],{"class":100},[72,4045,4028],{"class":440},[72,4047,443],{"class":100},[72,4049,447],{"class":446},[72,4051,4052],{"class":440},"Mutex",[72,4054,453],{"class":446},[72,4056,4057],{"class":473}," mpsc",[72,4059,453],{"class":446},[72,4061,4062],{"class":473}," oneshot",[72,4064,453],{"class":446},[72,4066,4067],{"class":440}," RwLock",[72,4069,453],{"class":446},[72,4071,4072],{"class":440}," Semaphore",[72,4074,453],{"class":446},[72,4076,4077],{"class":440}," Notify",[72,4079,459],{"class":446},[72,4081,4082],{"class":74,"line":107},[72,4083,465],{"emptyLinePlaceholder":464},[72,4085,4086,4088,4090,4092,4094],{"class":74,"line":122},[72,4087,470],{"class":446},[72,4089,2778],{"class":473},[72,4091,443],{"class":100},[72,4093,2783],{"class":473},[72,4095,2786],{"class":446},[72,4097,4098,4100,4102,4104,4106],{"class":74,"line":260},[72,4099,2791],{"class":100},[72,4101,2794],{"class":100},[72,4103,572],{"class":89},[72,4105,575],{"class":446},[72,4107,503],{"class":446},[72,4109,4110,4112],{"class":74,"line":274},[72,4111,596],{"class":78},[72,4113,4114],{"class":82}," 1) tokio::sync::Mutex：跨 .await 持锁时必须用它（std::Mutex 的守卫不是 Send，且会阻塞线程）\n",[72,4116,4117,4119,4122,4124,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145],{"class":74,"line":287},[72,4118,605],{"class":100},[72,4120,4121],{"class":508}," counter",[72,4123,611],{"class":100},[72,4125,4126],{"class":440}," Arc",[72,4128,443],{"class":100},[72,4130,3877],{"class":89},[72,4132,476],{"class":446},[72,4134,4052],{"class":440},[72,4136,443],{"class":100},[72,4138,3877],{"class":89},[72,4140,476],{"class":446},[72,4142,1330],{"class":89},[72,4144,2267],{"class":440},[72,4146,4147],{"class":446},"));\n",[72,4149,4150,4152,4154,4157,4159,4162],{"class":74,"line":301},[72,4151,605],{"class":100},[72,4153,3781],{"class":100},[72,4155,4156],{"class":508}," handles",[72,4158,611],{"class":100},[72,4160,4161],{"class":89}," vec!",[72,4163,4164],{"class":446},"[];\n",[72,4166,4167,4170,4172,4175,4177,4180,4182],{"class":74,"line":317},[72,4168,4169],{"class":100},"    for",[72,4171,3351],{"class":508},[72,4173,4174],{"class":100}," in",[72,4176,1869],{"class":89},[72,4178,4179],{"class":100},"..",[72,4181,3327],{"class":89},[72,4183,503],{"class":446},[72,4185,4186,4189,4192,4194,4196,4198,4201],{"class":74,"line":331},[72,4187,4188],{"class":100},"        let",[72,4190,4191],{"class":508}," c",[72,4193,611],{"class":100},[72,4195,4121],{"class":508},[72,4197,627],{"class":100},[72,4199,4200],{"class":89},"clone",[72,4202,1500],{"class":446},[72,4204,4205,4208,4210,4213,4215,4217,4219,4221,4223,4225,4228],{"class":74,"line":353},[72,4206,4207],{"class":508},"        handles",[72,4209,627],{"class":100},[72,4211,4212],{"class":89},"push",[72,4214,476],{"class":446},[72,4216,2778],{"class":440},[72,4218,443],{"class":100},[72,4220,2815],{"class":89},[72,4222,476],{"class":446},[72,4224,2791],{"class":100},[72,4226,4227],{"class":100}," move",[72,4229,503],{"class":446},[72,4231,4232,4235,4237,4240,4242,4244,4246,4249,4251,4253,4255,4257],{"class":74,"line":602},[72,4233,4234],{"class":100},"            let",[72,4236,3781],{"class":100},[72,4238,4239],{"class":508}," n",[72,4241,611],{"class":100},[72,4243,4191],{"class":508},[72,4245,627],{"class":100},[72,4247,4248],{"class":89},"lock",[72,4250,575],{"class":446},[72,4252,2879],{"class":100},[72,4254,3030],{"class":446},[72,4256,552],{"class":78},[72,4258,4259],{"class":82}," 异步锁，拿不到时让出线程而不是阻塞\n",[72,4261,4262,4265,4268,4271,4274],{"class":74,"line":657},[72,4263,4264],{"class":100},"            *",[72,4266,4267],{"class":508},"n",[72,4269,4270],{"class":100}," +=",[72,4272,4273],{"class":89}," 1",[72,4275,688],{"class":446},[72,4277,4278],{"class":74,"line":691},[72,4279,4280],{"class":446},"        }));\n",[72,4282,4283],{"class":74,"line":720},[72,4284,1936],{"class":446},[72,4286,4287,4289,4292,4294,4296,4298,4300,4302,4304,4307],{"class":74,"line":725},[72,4288,4169],{"class":100},[72,4290,4291],{"class":508}," h",[72,4293,4174],{"class":100},[72,4295,4156],{"class":508},[72,4297,616],{"class":446},[72,4299,4291],{"class":508},[72,4301,2916],{"class":100},[72,4303,1497],{"class":89},[72,4305,4306],{"class":446},"();",[72,4308,1181],{"class":446},[72,4310,4311,4313,4315,4317,4319,4321,4323,4326,4329,4331,4333,4335,4337,4339,4341],{"class":74,"line":733},[72,4312,694],{"class":89},[72,4314,476],{"class":446},[72,4316,699],{"class":93},[72,4318,742],{"class":446},[72,4320,699],{"class":93},[72,4322,453],{"class":446},[72,4324,4325],{"class":100}," *",[72,4327,4328],{"class":508},"counter",[72,4330,627],{"class":100},[72,4332,4248],{"class":89},[72,4334,575],{"class":446},[72,4336,2879],{"class":100},[72,4338,712],{"class":446},[72,4340,552],{"class":78},[72,4342,4343],{"class":82}," 10\n",[72,4345,4346],{"class":74,"line":769},[72,4347,465],{"emptyLinePlaceholder":464},[72,4349,4350,4352],{"class":74,"line":774},[72,4351,596],{"class":78},[72,4353,4354],{"class":82}," 2) mpsc 通道：多生产者单消费者，任务间通信的首选\n",[72,4356,4357,4359,4361,4364,4366,4368,4371,4373,4375,4377,4379,4382,4384,4386,4388,4391,4394,4396,4398],{"class":74,"line":782},[72,4358,605],{"class":100},[72,4360,3163],{"class":446},[72,4362,4363],{"class":508},"tx",[72,4365,453],{"class":446},[72,4367,3781],{"class":100},[72,4369,4370],{"class":508}," rx",[72,4372,682],{"class":446},[72,4374,611],{"class":100},[72,4376,4057],{"class":440},[72,4378,443],{"class":100},[72,4380,4381],{"class":89},"channel",[72,4383,443],{"class":100},[72,4385,543],{"class":446},[72,4387,546],{"class":440},[72,4389,4390],{"class":446},">(",[72,4392,4393],{"class":89},"100",[72,4395,712],{"class":446},[72,4397,552],{"class":78},[72,4399,4400],{"class":82}," 100 是缓冲容量，满了 send 会 .await 背压\n",[72,4402,4403,4405,4407,4409,4411,4413,4415],{"class":74,"line":797},[72,4404,3269],{"class":440},[72,4406,443],{"class":100},[72,4408,2815],{"class":89},[72,4410,476],{"class":446},[72,4412,2791],{"class":100},[72,4414,4227],{"class":100},[72,4416,503],{"class":446},[72,4418,4419,4422,4425,4427,4429,4431,4434],{"class":74,"line":829},[72,4420,4421],{"class":100},"        for",[72,4423,4424],{"class":508}," i",[72,4426,4174],{"class":100},[72,4428,1869],{"class":89},[72,4430,4179],{"class":100},[72,4432,4433],{"class":89},"3",[72,4435,503],{"class":446},[72,4437,4438,4441,4443,4446,4448,4451,4453,4456,4458,4461,4463,4465,4467,4469,4471],{"class":74,"line":849},[72,4439,4440],{"class":508},"            tx",[72,4442,627],{"class":100},[72,4444,4445],{"class":89},"send",[72,4447,476],{"class":446},[72,4449,4450],{"class":89},"format!",[72,4452,476],{"class":446},[72,4454,4455],{"class":93},"\"消息",[72,4457,447],{"class":446},[72,4459,4460],{"class":93},"i",[72,4462,707],{"class":446},[72,4464,699],{"class":93},[72,4466,1804],{"class":446},[72,4468,2916],{"class":100},[72,4470,1497],{"class":89},[72,4472,1500],{"class":446},[72,4474,4475],{"class":74,"line":858},[72,4476,4477],{"class":446},"        }\n",[72,4479,4480],{"class":74,"line":2023},[72,4481,2891],{"class":446},[72,4483,4484,4487,4489,4491,4493,4496,4498,4500,4502,4504,4507,4509,4511,4513,4515],{"class":74,"line":2028},[72,4485,4486],{"class":100},"    while",[72,4488,1299],{"class":100},[72,4490,1302],{"class":440},[72,4492,476],{"class":446},[72,4494,4495],{"class":508},"msg",[72,4497,682],{"class":446},[72,4499,611],{"class":100},[72,4501,4370],{"class":508},[72,4503,627],{"class":100},[72,4505,4506],{"class":89},"recv",[72,4508,575],{"class":446},[72,4510,2879],{"class":100},[72,4512,616],{"class":446},[72,4514,552],{"class":78},[72,4516,4517],{"class":82}," 所有 tx 都 drop 后 recv 返回 None，循环结束\n",[72,4519,4520,4523,4525,4528,4530,4532,4534,4536],{"class":74,"line":2036},[72,4521,4522],{"class":89},"        println!",[72,4524,476],{"class":446},[72,4526,4527],{"class":93},"\"收到: ",[72,4529,447],{"class":446},[72,4531,4495],{"class":93},[72,4533,707],{"class":446},[72,4535,699],{"class":93},[72,4537,766],{"class":446},[72,4539,4540],{"class":74,"line":2069},[72,4541,1936],{"class":446},[72,4543,4544],{"class":74,"line":2096},[72,4545,465],{"emptyLinePlaceholder":464},[72,4547,4548,4550],{"class":74,"line":2132},[72,4549,596],{"class":78},[72,4551,4552],{"class":82}," 3) oneshot：一次性应答，常用于「请求-响应」式取消通知\n",[72,4554,4555,4557,4559,4561,4563,4565,4567,4569,4571,4573,4575,4577,4579,4581,4583],{"class":74,"line":2137},[72,4556,605],{"class":100},[72,4558,3163],{"class":446},[72,4560,4363],{"class":508},[72,4562,453],{"class":446},[72,4564,4370],{"class":508},[72,4566,682],{"class":446},[72,4568,611],{"class":100},[72,4570,4062],{"class":440},[72,4572,443],{"class":100},[72,4574,4381],{"class":89},[72,4576,443],{"class":100},[72,4578,543],{"class":446},[72,4580,676],{"class":100},[72,4582,1718],{"class":440},[72,4584,4585],{"class":446},">();\n",[72,4587,4588,4590,4592,4594,4596,4598,4600,4602,4604,4606,4608,4611,4613,4615,4617,4620,4622],{"class":74,"line":2144},[72,4589,3269],{"class":440},[72,4591,443],{"class":100},[72,4593,2815],{"class":89},[72,4595,476],{"class":446},[72,4597,2791],{"class":100},[72,4599,4227],{"class":100},[72,4601,616],{"class":446},[72,4603,1299],{"class":100},[72,4605,3351],{"class":508},[72,4607,611],{"class":100},[72,4609,4610],{"class":508}," tx",[72,4612,627],{"class":100},[72,4614,4445],{"class":89},[72,4616,476],{"class":446},[72,4618,4619],{"class":93},"\"pong\"",[72,4621,712],{"class":446},[72,4623,4624],{"class":446}," });\n",[72,4626,4628,4630,4632,4634,4636,4638,4640,4642,4644,4646],{"class":74,"line":4627},33,[72,4629,694],{"class":89},[72,4631,476],{"class":446},[72,4633,699],{"class":93},[72,4635,742],{"class":446},[72,4637,699],{"class":93},[72,4639,453],{"class":446},[72,4641,4370],{"class":508},[72,4643,2916],{"class":100},[72,4645,1497],{"class":89},[72,4647,2020],{"class":446},[72,4649,4651],{"class":74,"line":4650},34,[72,4652,465],{"emptyLinePlaceholder":464},[72,4654,4656,4658],{"class":74,"line":4655},35,[72,4657,596],{"class":78},[72,4659,4660],{"class":82}," 4) RwLock \u002F Semaphore \u002F Notify 与 std 对应物语义类似，但都是 async 版\n",[72,4662,4664,4666,4669,4671,4673,4675,4677,4679,4682,4684,4686,4688,4690,4692,4694],{"class":74,"line":4663},36,[72,4665,605],{"class":100},[72,4667,4668],{"class":508}," sem",[72,4670,611],{"class":100},[72,4672,4126],{"class":440},[72,4674,443],{"class":100},[72,4676,3877],{"class":89},[72,4678,476],{"class":446},[72,4680,4681],{"class":440},"Semaphore",[72,4683,443],{"class":100},[72,4685,3877],{"class":89},[72,4687,476],{"class":446},[72,4689,4433],{"class":89},[72,4691,2483],{"class":446},[72,4693,552],{"class":78},[72,4695,4696],{"class":82}," 限制并发度：最多 3 个任务同时进入临界区\n",[72,4698,4700,4702,4705,4707,4709,4711,4714,4716,4718,4720],{"class":74,"line":4699},37,[72,4701,605],{"class":100},[72,4703,4704],{"class":508}," permit",[72,4706,611],{"class":100},[72,4708,4668],{"class":508},[72,4710,627],{"class":100},[72,4712,4713],{"class":89},"acquire",[72,4715,575],{"class":446},[72,4717,2916],{"class":100},[72,4719,1497],{"class":89},[72,4721,1500],{"class":446},[72,4723,4725,4728,4730,4733,4735,4737],{"class":74,"line":4724},38,[72,4726,4727],{"class":89},"    drop",[72,4729,476],{"class":446},[72,4731,4732],{"class":508},"permit",[72,4734,712],{"class":446},[72,4736,552],{"class":78},[72,4738,4739],{"class":82}," 释放名额\n",[72,4741,4743,4745,4747,4749,4751,4753,4755,4757,4759],{"class":74,"line":4742},39,[72,4744,605],{"class":100},[72,4746,3351],{"class":508},[72,4748,611],{"class":100},[72,4750,4077],{"class":440},[72,4752,443],{"class":100},[72,4754,3877],{"class":89},[72,4756,4306],{"class":446},[72,4758,552],{"class":78},[72,4760,4761],{"class":82}," 事件通知：notify_one\u002Fnotify_waiters + notified().await\n",[72,4763,4765,4767,4769,4771,4773,4775,4777,4779,4781,4784],{"class":74,"line":4764},40,[72,4766,605],{"class":100},[72,4768,3351],{"class":508},[72,4770,611],{"class":100},[72,4772,4067],{"class":440},[72,4774,443],{"class":100},[72,4776,3877],{"class":89},[72,4778,476],{"class":446},[72,4780,1330],{"class":89},[72,4782,4783],{"class":440},"u8",[72,4785,766],{"class":446},[72,4787,4789],{"class":74,"line":4788},41,[72,4790,560],{"class":446},[15,4792,4793,4796,4797,4800,4801,4803],{},[19,4794,4795],{},"为什么通道容量要有限（100 而不是 unbounded）？"," 有限缓冲提供了",[19,4798,4799],{},"背压（backpressure）","：生产快于消费时，",[53,4802,4445],{}," 会挂起等位置空出来，内存不会无限膨胀。无界通道在流量高峰时就是内存炸弹。这是 tokio 引导你写出稳态系统的典型设计。",[24,4805],{},[27,4807,4809],{"id":4808},"第-4-章-reqwesthttp-客户端","第 4 章 reqwest：HTTP 客户端",[58,4811,4813],{"id":4812},"_41-它解决什么问题","4.1 它解决什么问题",[15,4815,4816],{},"发 HTTP 请求调用 REST API、下载文件。reqwest 构建在 tokio + hyper 之上，提供符合直觉的高层 API，是生态绝对主流。",[63,4818,4820],{"className":144,"code":4819,"language":146,"meta":68,"style":68},"[dependencies]\nreqwest = { version = \"0.12\", features = [\"json\"] }\ntokio = { version = \"1\", features = [\"full\"] }\nserde = { version = \"1\", features = [\"derive\"] }\n",[53,4821,4822,4826,4830,4834],{"__ignoreMap":68},[72,4823,4824],{"class":74,"line":75},[72,4825,153],{},[72,4827,4828],{"class":74,"line":86},[72,4829,168],{},[72,4831,4832],{"class":74,"line":107},[72,4833,163],{},[72,4835,4836],{"class":74,"line":122},[72,4837,158],{},[58,4839,4841],{"id":4840},"_42-get-post-与-json","4.2 GET \u002F POST 与 JSON",[63,4843,4845],{"className":428,"code":4844,"language":430,"meta":68,"style":68},"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",[53,4846,4847,4865,4869,4885,4911,4915,4927,4953,4957,4969,4991,4998,5020,5025,5039,5046,5090,5094,5101,5127,5131,5140,5146,5165,5171],{"__ignoreMap":68},[72,4848,4849,4851,4853,4855,4857,4859,4861,4863],{"class":74,"line":75},[72,4850,437],{"class":100},[72,4852,97],{"class":440},[72,4854,443],{"class":100},[72,4856,447],{"class":446},[72,4858,450],{"class":440},[72,4860,453],{"class":446},[72,4862,456],{"class":440},[72,4864,459],{"class":446},[72,4866,4867],{"class":74,"line":86},[72,4868,465],{"emptyLinePlaceholder":464},[72,4870,4871,4873,4875,4877,4879,4881,4883],{"class":74,"line":107},[72,4872,470],{"class":446},[72,4874,177],{"class":473},[72,4876,476],{"class":446},[72,4878,450],{"class":440},[72,4880,453],{"class":446},[72,4882,489],{"class":440},[72,4884,492],{"class":446},[72,4886,4887,4889,4892,4894,4896,4898,4900,4902,4905,4907,4909],{"class":74,"line":122},[72,4888,497],{"class":100},[72,4890,4891],{"class":440}," Post",[72,4893,616],{"class":446},[72,4895,2454],{"class":508},[72,4897,512],{"class":100},[72,4899,932],{"class":440},[72,4901,453],{"class":446},[72,4903,4904],{"class":508}," title",[72,4906,512],{"class":100},[72,4908,515],{"class":440},[72,4910,1181],{"class":446},[72,4912,4913],{"class":74,"line":260},[72,4914,465],{"emptyLinePlaceholder":464},[72,4916,4917,4919,4921,4923,4925],{"class":74,"line":274},[72,4918,470],{"class":446},[72,4920,177],{"class":473},[72,4922,476],{"class":446},[72,4924,479],{"class":440},[72,4926,492],{"class":446},[72,4928,4929,4931,4934,4936,4938,4940,4942,4944,4947,4949,4951],{"class":74,"line":287},[72,4930,497],{"class":100},[72,4932,4933],{"class":440}," NewPost",[72,4935,616],{"class":446},[72,4937,4904],{"class":508},[72,4939,512],{"class":100},[72,4941,515],{"class":440},[72,4943,453],{"class":446},[72,4945,4946],{"class":508}," body",[72,4948,512],{"class":100},[72,4950,515],{"class":440},[72,4952,1181],{"class":446},[72,4954,4955],{"class":74,"line":301},[72,4956,465],{"emptyLinePlaceholder":464},[72,4958,4959,4961,4963,4965,4967],{"class":74,"line":317},[72,4960,470],{"class":446},[72,4962,2778],{"class":473},[72,4964,443],{"class":100},[72,4966,2783],{"class":473},[72,4968,2786],{"class":446},[72,4970,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989],{"class":74,"line":331},[72,4972,2791],{"class":100},[72,4974,2794],{"class":100},[72,4976,572],{"class":89},[72,4978,575],{"class":446},[72,4980,578],{"class":100},[72,4982,129],{"class":440},[72,4984,443],{"class":100},[72,4986,586],{"class":440},[72,4988,589],{"class":446},[72,4990,503],{"class":446},[72,4992,4993,4995],{"class":74,"line":353},[72,4994,596],{"class":78},[72,4996,4997],{"class":82}," 最简单的 GET\n",[72,4999,5000,5002,5004,5006,5008,5010,5013,5015,5018],{"class":74,"line":602},[72,5001,605],{"class":100},[72,5003,4946],{"class":508},[72,5005,611],{"class":100},[72,5007,129],{"class":440},[72,5009,443],{"class":100},[72,5011,5012],{"class":89},"get",[72,5014,476],{"class":446},[72,5016,5017],{"class":93},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F1\"",[72,5019,1771],{"class":446},[72,5021,5022],{"class":74,"line":657},[72,5023,5024],{"class":100},"        .await?\n",[72,5026,5027,5029,5032,5034,5036],{"class":74,"line":691},[72,5028,1776],{"class":100},[72,5030,5031],{"class":89},"text",[72,5033,575],{"class":446},[72,5035,1033],{"class":78},[72,5037,5038],{"class":82}," .text() 拿字符串；.bytes() 拿字节；.json() 直接反序列化\n",[72,5040,5041,5044],{"class":74,"line":720},[72,5042,5043],{"class":100},"        .await?",[72,5045,688],{"class":446},[72,5047,5048,5050,5052,5055,5057,5060,5062,5064,5067,5069,5071,5074,5076,5079,5081,5083,5085,5087],{"class":74,"line":725},[72,5049,694],{"class":89},[72,5051,476],{"class":446},[72,5053,5054],{"class":93},"\"原始响应: ",[72,5056,742],{"class":446},[72,5058,5059],{"class":93},"...\"",[72,5061,453],{"class":446},[72,5063,1715],{"class":100},[72,5065,5066],{"class":508},"body",[72,5068,1316],{"class":446},[72,5070,4179],{"class":100},[72,5072,5073],{"class":89},"50",[72,5075,627],{"class":446},[72,5077,5078],{"class":89},"min",[72,5080,476],{"class":446},[72,5082,5066],{"class":508},[72,5084,627],{"class":100},[72,5086,2017],{"class":89},[72,5088,5089],{"class":446},"())]);\n",[72,5091,5092],{"class":74,"line":733},[72,5093,465],{"emptyLinePlaceholder":464},[72,5095,5096,5098],{"class":74,"line":769},[72,5097,596],{"class":78},[72,5099,5100],{"class":82}," GET + 反序列化为结构体（依赖 serde）\n",[72,5102,5103,5105,5108,5110,5112,5114,5116,5118,5120,5122,5125],{"class":74,"line":774},[72,5104,605],{"class":100},[72,5106,5107],{"class":508}," post",[72,5109,512],{"class":100},[72,5111,4891],{"class":440},[72,5113,611],{"class":100},[72,5115,129],{"class":440},[72,5117,443],{"class":100},[72,5119,5012],{"class":89},[72,5121,476],{"class":446},[72,5123,5124],{"class":93},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\u002F2\"",[72,5126,1771],{"class":446},[72,5128,5129],{"class":74,"line":782},[72,5130,5024],{"class":100},[72,5132,5133,5135,5138],{"class":74,"line":797},[72,5134,1776],{"class":100},[72,5136,5137],{"class":89},"json",[72,5139,3984],{"class":446},[72,5141,5142,5144],{"class":74,"line":829},[72,5143,5043],{"class":100},[72,5145,688],{"class":446},[72,5147,5148,5150,5152,5154,5156,5159,5161,5163],{"class":74,"line":849},[72,5149,694],{"class":89},[72,5151,476],{"class":446},[72,5153,699],{"class":93},[72,5155,447],{"class":446},[72,5157,5158],{"class":93},"post:?",[72,5160,707],{"class":446},[72,5162,699],{"class":93},[72,5164,766],{"class":446},[72,5166,5167,5169],{"class":74,"line":858},[72,5168,852],{"class":440},[72,5170,855],{"class":446},[72,5172,5173],{"class":74,"line":2023},[72,5174,560],{"class":446},[58,5176,5178],{"id":5177},"_43-复用-client生产环境的正确姿势","4.3 复用 Client：生产环境的正确姿势",[63,5180,5182],{"className":428,"code":5181,"language":430,"meta":68,"style":68},"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",[53,5183,5184,5200,5204,5216,5238,5245,5252,5259,5282,5307,5334,5354,5367,5371,5378,5390,5404,5453,5461,5467,5491,5495,5502,5512,5524,5554,5573,5581,5587,5591,5598,5642,5666,5672],{"__ignoreMap":68},[72,5185,5186,5188,5190,5192,5194,5196,5198],{"class":74,"line":75},[72,5187,437],{"class":100},[72,5189,1121],{"class":440},[72,5191,443],{"class":100},[72,5193,2846],{"class":440},[72,5195,443],{"class":100},[72,5197,2865],{"class":440},[72,5199,688],{"class":446},[72,5201,5202],{"class":74,"line":86},[72,5203,465],{"emptyLinePlaceholder":464},[72,5205,5206,5208,5210,5212,5214],{"class":74,"line":107},[72,5207,470],{"class":446},[72,5209,2778],{"class":473},[72,5211,443],{"class":100},[72,5213,2783],{"class":473},[72,5215,2786],{"class":446},[72,5217,5218,5220,5222,5224,5226,5228,5230,5232,5234,5236],{"class":74,"line":122},[72,5219,2791],{"class":100},[72,5221,2794],{"class":100},[72,5223,572],{"class":89},[72,5225,575],{"class":446},[72,5227,578],{"class":100},[72,5229,129],{"class":440},[72,5231,443],{"class":100},[72,5233,586],{"class":440},[72,5235,589],{"class":446},[72,5237,503],{"class":446},[72,5239,5240,5242],{"class":74,"line":260},[72,5241,596],{"class":78},[72,5243,5244],{"class":82}," 为什么要显式建 Client 而不是每次 reqwest::get？\n",[72,5246,5247,5249],{"class":74,"line":274},[72,5248,596],{"class":78},[72,5250,5251],{"class":82}," Client 内部维护连接池（HTTP keep-alive），复用 TCP\u002FTLS 连接，\n",[72,5253,5254,5256],{"class":74,"line":287},[72,5255,596],{"class":78},[72,5257,5258],{"class":82}," 高频请求下性能差距巨大；还能统一配置超时、默认 header、代理。\n",[72,5260,5261,5263,5266,5268,5270,5272,5275,5277,5280],{"class":74,"line":301},[72,5262,605],{"class":100},[72,5264,5265],{"class":508}," client",[72,5267,611],{"class":100},[72,5269,129],{"class":440},[72,5271,443],{"class":100},[72,5273,5274],{"class":440},"Client",[72,5276,443],{"class":100},[72,5278,5279],{"class":89},"builder",[72,5281,3984],{"class":446},[72,5283,5284,5286,5288,5290,5292,5294,5296,5298,5300,5302,5304],{"class":74,"line":317},[72,5285,1776],{"class":100},[72,5287,3413],{"class":89},[72,5289,476],{"class":446},[72,5291,2865],{"class":440},[72,5293,443],{"class":100},[72,5295,2870],{"class":89},[72,5297,476],{"class":446},[72,5299,3327],{"class":89},[72,5301,1804],{"class":446},[72,5303,919],{"class":78},[72,5305,5306],{"class":82}," 总超时，防止请求永远挂起拖垮服务\n",[72,5308,5309,5311,5314,5316,5318,5320,5322,5324,5326,5328,5331],{"class":74,"line":331},[72,5310,1776],{"class":100},[72,5312,5313],{"class":89},"connect_timeout",[72,5315,476],{"class":446},[72,5317,2865],{"class":440},[72,5319,443],{"class":100},[72,5321,2870],{"class":89},[72,5323,476],{"class":446},[72,5325,4433],{"class":89},[72,5327,1804],{"class":446},[72,5329,5330],{"class":78},"     \u002F\u002F",[72,5332,5333],{"class":82}," 连接建立超时单独设置，更快失败\n",[72,5335,5336,5338,5341,5343,5346,5348,5351],{"class":74,"line":353},[72,5337,1776],{"class":100},[72,5339,5340],{"class":89},"user_agent",[72,5342,476],{"class":446},[72,5344,5345],{"class":93},"\"my-app\u002F1.0\"",[72,5347,682],{"class":446},[72,5349,5350],{"class":78},"                    \u002F\u002F",[72,5352,5353],{"class":82}," 很多 API 拒绝无 UA 的请求\n",[72,5355,5356,5358,5361,5363,5365],{"class":74,"line":602},[72,5357,1776],{"class":100},[72,5359,5360],{"class":89},"build",[72,5362,575],{"class":446},[72,5364,685],{"class":100},[72,5366,688],{"class":446},[72,5368,5369],{"class":74,"line":657},[72,5370,465],{"emptyLinePlaceholder":464},[72,5372,5373,5375],{"class":74,"line":691},[72,5374,596],{"class":78},[72,5376,5377],{"class":82}," POST JSON\n",[72,5379,5380,5382,5385,5387],{"class":74,"line":720},[72,5381,605],{"class":100},[72,5383,5384],{"class":508}," resp",[72,5386,611],{"class":100},[72,5388,5389],{"class":508}," client\n",[72,5391,5392,5394,5397,5399,5402],{"class":74,"line":725},[72,5393,1776],{"class":100},[72,5395,5396],{"class":89},"post",[72,5398,476],{"class":446},[72,5400,5401],{"class":93},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\"",[72,5403,1771],{"class":446},[72,5405,5406,5408,5410,5412,5414,5417,5419,5421,5423,5426,5428,5430,5432,5434,5436,5439,5441,5443,5445,5448,5450],{"class":74,"line":733},[72,5407,1776],{"class":100},[72,5409,5137],{"class":89},[72,5411,476],{"class":446},[72,5413,676],{"class":100},[72,5415,5416],{"class":440},"NewPost",[72,5418,616],{"class":446},[72,5420,4904],{"class":508},[72,5422,512],{"class":100},[72,5424,5425],{"class":93}," \"t\"",[72,5427,627],{"class":100},[72,5429,630],{"class":89},[72,5431,633],{"class":446},[72,5433,4946],{"class":508},[72,5435,512],{"class":100},[72,5437,5438],{"class":93}," \"b\"",[72,5440,627],{"class":100},[72,5442,630],{"class":89},[72,5444,575],{"class":446},[72,5446,5447],{"class":446}," })",[72,5449,552],{"class":78},[72,5451,5452],{"class":82}," 自动序列化 + 设 Content-Type\n",[72,5454,5455,5457,5459],{"class":74,"line":769},[72,5456,1776],{"class":100},[72,5458,4445],{"class":89},[72,5460,3984],{"class":446},[72,5462,5463,5465],{"class":74,"line":774},[72,5464,5043],{"class":100},[72,5466,688],{"class":446},[72,5468,5469,5471,5473,5476,5478,5480,5482,5484,5486,5489],{"class":74,"line":782},[72,5470,694],{"class":89},[72,5472,476],{"class":446},[72,5474,5475],{"class":93},"\"状态: ",[72,5477,742],{"class":446},[72,5479,699],{"class":93},[72,5481,453],{"class":446},[72,5483,5384],{"class":508},[72,5485,627],{"class":100},[72,5487,5488],{"class":89},"status",[72,5490,2020],{"class":446},[72,5492,5493],{"class":74,"line":797},[72,5494,465],{"emptyLinePlaceholder":464},[72,5496,5497,5499],{"class":74,"line":829},[72,5498,596],{"class":78},[72,5500,5501],{"class":82}," GET 带查询参数、自定义 header\n",[72,5503,5504,5506,5508,5510],{"class":74,"line":849},[72,5505,605],{"class":100},[72,5507,5384],{"class":508},[72,5509,611],{"class":100},[72,5511,5389],{"class":508},[72,5513,5514,5516,5518,5520,5522],{"class":74,"line":858},[72,5515,1776],{"class":100},[72,5517,5012],{"class":89},[72,5519,476],{"class":446},[72,5521,5401],{"class":93},[72,5523,1771],{"class":446},[72,5525,5526,5528,5531,5533,5535,5538,5541,5543,5546,5549,5551],{"class":74,"line":2023},[72,5527,1776],{"class":100},[72,5529,5530],{"class":89},"query",[72,5532,476],{"class":446},[72,5534,676],{"class":100},[72,5536,5537],{"class":446},"[(",[72,5539,5540],{"class":93},"\"userId\"",[72,5542,453],{"class":446},[72,5544,5545],{"class":93}," \"1\"",[72,5547,5548],{"class":446},")])",[72,5550,954],{"class":78},[72,5552,5553],{"class":82}," 自动做 URL 编码，别手拼字符串\n",[72,5555,5556,5558,5561,5563,5566,5568,5571],{"class":74,"line":2028},[72,5557,1776],{"class":100},[72,5559,5560],{"class":89},"header",[72,5562,476],{"class":446},[72,5564,5565],{"class":93},"\"Authorization\"",[72,5567,453],{"class":446},[72,5569,5570],{"class":93}," \"Bearer token123\"",[72,5572,1771],{"class":446},[72,5574,5575,5577,5579],{"class":74,"line":2036},[72,5576,1776],{"class":100},[72,5578,4445],{"class":89},[72,5580,3984],{"class":446},[72,5582,5583,5585],{"class":74,"line":2069},[72,5584,5043],{"class":100},[72,5586,688],{"class":446},[72,5588,5589],{"class":74,"line":2096},[72,5590,465],{"emptyLinePlaceholder":464},[72,5592,5593,5595],{"class":74,"line":2132},[72,5594,596],{"class":78},[72,5596,5597],{"class":82}," error_for_status：把 4xx\u002F5xx 变成 Err，否则 reqwest 认为 HTTP 层成功就是成功\n",[72,5599,5600,5602,5605,5607,5609,5611,5614,5616,5618,5620,5622,5624,5626,5629,5631,5634,5636,5638,5640],{"class":74,"line":2137},[72,5601,605],{"class":100},[72,5603,5604],{"class":508}," posts",[72,5606,512],{"class":100},[72,5608,967],{"class":440},[72,5610,543],{"class":446},[72,5612,5613],{"class":508},"serde_json",[72,5615,443],{"class":100},[72,5617,1144],{"class":440},[72,5619,1731],{"class":446},[72,5621,611],{"class":100},[72,5623,5384],{"class":508},[72,5625,627],{"class":100},[72,5627,5628],{"class":89},"error_for_status",[72,5630,575],{"class":446},[72,5632,5633],{"class":100},"?.",[72,5635,5137],{"class":89},[72,5637,575],{"class":446},[72,5639,3737],{"class":100},[72,5641,688],{"class":446},[72,5643,5644,5646,5648,5651,5653,5656,5658,5660,5662,5664],{"class":74,"line":2144},[72,5645,694],{"class":89},[72,5647,476],{"class":446},[72,5649,5650],{"class":93},"\"拿到 ",[72,5652,742],{"class":446},[72,5654,5655],{"class":93}," 条\"",[72,5657,453],{"class":446},[72,5659,5604],{"class":508},[72,5661,627],{"class":100},[72,5663,2017],{"class":89},[72,5665,2020],{"class":446},[72,5667,5668,5670],{"class":74,"line":4627},[72,5669,852],{"class":440},[72,5671,855],{"class":446},[72,5673,5674],{"class":74,"line":4650},[72,5675,560],{"class":446},[15,5677,5678,5681],{},[19,5679,5680],{},"为什么一定要设超时？"," 不设超时的 HTTP 调用是对下游故障敞开的：对方服务挂起时你的每个请求都永远占着一个任务和连接，最终耗尽资源级联雪崩。所有出网调用都必须有超时，这是服务端开发的第一戒律。",[58,5683,5685],{"id":5684},"_44-大文件流式下载","4.4 大文件流式下载",[63,5687,5689],{"className":428,"code":5688,"language":430,"meta":68,"style":68},"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",[53,5690,5691,5705,5721,5725,5786,5825,5862,5866,5873,5910,5935,5939,5945],{"__ignoreMap":68},[72,5692,5693,5695,5698,5700,5703],{"class":74,"line":75},[72,5694,437],{"class":100},[72,5696,5697],{"class":440}," futures_util",[72,5699,443],{"class":100},[72,5701,5702],{"class":440},"StreamExt",[72,5704,688],{"class":446},[72,5706,5707,5709,5711,5713,5715,5717,5719],{"class":74,"line":86},[72,5708,437],{"class":100},[72,5710,114],{"class":440},[72,5712,443],{"class":100},[72,5714,3649],{"class":440},[72,5716,443],{"class":100},[72,5718,3656],{"class":440},[72,5720,688],{"class":446},[72,5722,5723],{"class":74,"line":107},[72,5724,465],{"emptyLinePlaceholder":464},[72,5726,5727,5729,5731,5734,5736,5739,5741,5743,5746,5748,5750,5752,5755,5757,5759,5761,5763,5766,5768,5770,5772,5774,5776,5778,5780,5782,5784],{"class":74,"line":122},[72,5728,2791],{"class":100},[72,5730,2794],{"class":100},[72,5732,5733],{"class":89}," download",[72,5735,476],{"class":446},[72,5737,5738],{"class":508},"client",[72,5740,512],{"class":100},[72,5742,1715],{"class":100},[72,5744,5745],{"class":440},"reqwest",[72,5747,443],{"class":100},[72,5749,5274],{"class":440},[72,5751,453],{"class":446},[72,5753,5754],{"class":508}," url",[72,5756,512],{"class":100},[72,5758,1715],{"class":100},[72,5760,1718],{"class":440},[72,5762,453],{"class":446},[72,5764,5765],{"class":508}," path",[72,5767,512],{"class":100},[72,5769,1715],{"class":100},[72,5771,1718],{"class":440},[72,5773,682],{"class":446},[72,5775,578],{"class":100},[72,5777,129],{"class":440},[72,5779,443],{"class":100},[72,5781,586],{"class":440},[72,5783,589],{"class":446},[72,5785,503],{"class":446},[72,5787,5788,5790,5792,5794,5796,5798,5800,5802,5804,5807,5809,5811,5813,5815,5817,5819,5821,5823],{"class":74,"line":260},[72,5789,605],{"class":100},[72,5791,3781],{"class":100},[72,5793,5384],{"class":508},[72,5795,611],{"class":100},[72,5797,5265],{"class":508},[72,5799,627],{"class":100},[72,5801,5012],{"class":89},[72,5803,476],{"class":446},[72,5805,5806],{"class":508},"url",[72,5808,682],{"class":446},[72,5810,627],{"class":100},[72,5812,4445],{"class":89},[72,5814,575],{"class":446},[72,5816,3901],{"class":100},[72,5818,5628],{"class":89},[72,5820,575],{"class":446},[72,5822,685],{"class":100},[72,5824,688],{"class":446},[72,5826,5827,5829,5831,5834,5836,5838,5840,5842,5844,5846,5848,5850,5852,5854,5856,5858,5860],{"class":74,"line":274},[72,5828,605],{"class":100},[72,5830,3781],{"class":100},[72,5832,5833],{"class":508}," file",[72,5835,611],{"class":100},[72,5837,114],{"class":440},[72,5839,443],{"class":100},[72,5841,1685],{"class":440},[72,5843,443],{"class":100},[72,5845,3793],{"class":440},[72,5847,443],{"class":100},[72,5849,3798],{"class":89},[72,5851,476],{"class":446},[72,5853,1710],{"class":508},[72,5855,682],{"class":446},[72,5857,2916],{"class":100},[72,5859,1497],{"class":89},[72,5861,1500],{"class":446},[72,5863,5864],{"class":74,"line":287},[72,5865,465],{"emptyLinePlaceholder":464},[72,5867,5868,5870],{"class":74,"line":301},[72,5869,596],{"class":78},[72,5871,5872],{"class":82}," bytes_stream() 边下边写，内存占用恒定。如果把整个 body 读进内存，2GB 文件就占 2GB 内存\n",[72,5874,5875,5877,5879,5881,5883,5886,5888,5890,5892,5894,5897,5899,5901,5904,5906,5908],{"class":74,"line":317},[72,5876,4486],{"class":100},[72,5878,1299],{"class":100},[72,5880,1302],{"class":440},[72,5882,476],{"class":446},[72,5884,5885],{"class":508},"chunk",[72,5887,682],{"class":446},[72,5889,611],{"class":100},[72,5891,5384],{"class":508},[72,5893,627],{"class":100},[72,5895,5896],{"class":89},"bytes_stream",[72,5898,575],{"class":446},[72,5900,627],{"class":100},[72,5902,5903],{"class":89},"next",[72,5905,575],{"class":446},[72,5907,2879],{"class":100},[72,5909,503],{"class":446},[72,5911,5912,5915,5917,5919,5921,5923,5925,5927,5929,5931,5933],{"class":74,"line":331},[72,5913,5914],{"class":508},"        file",[72,5916,627],{"class":100},[72,5918,3819],{"class":89},[72,5920,476],{"class":446},[72,5922,676],{"class":100},[72,5924,5885],{"class":508},[72,5926,685],{"class":100},[72,5928,682],{"class":446},[72,5930,2916],{"class":100},[72,5932,1497],{"class":89},[72,5934,1500],{"class":446},[72,5936,5937],{"class":74,"line":353},[72,5938,1936],{"class":446},[72,5940,5941,5943],{"class":74,"line":602},[72,5942,852],{"class":440},[72,5944,855],{"class":446},[72,5946,5947],{"class":74,"line":657},[72,5948,560],{"class":446},[58,5950,5952],{"id":5951},"_45-表单与-multipart-上传","4.5 表单与 multipart 上传",[63,5954,5956],{"className":428,"code":5955,"language":430,"meta":68,"style":68},"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",[53,5957,5958,5995,6002,6018,6054,6066,6070,6077,6128,6141,6167,6185,6203,6237,6243],{"__ignoreMap":68},[72,5959,5960,5962,5964,5967,5969,5971,5973,5975,5977,5979,5981,5983,5985,5987,5989,5991,5993],{"class":74,"line":75},[72,5961,2791],{"class":100},[72,5963,2794],{"class":100},[72,5965,5966],{"class":89}," forms",[72,5968,476],{"class":446},[72,5970,5738],{"class":508},[72,5972,512],{"class":100},[72,5974,1715],{"class":100},[72,5976,5745],{"class":440},[72,5978,443],{"class":100},[72,5980,5274],{"class":440},[72,5982,682],{"class":446},[72,5984,578],{"class":100},[72,5986,129],{"class":440},[72,5988,443],{"class":100},[72,5990,586],{"class":440},[72,5992,589],{"class":446},[72,5994,503],{"class":446},[72,5996,5997,5999],{"class":74,"line":86},[72,5998,596],{"class":78},[72,6000,6001],{"class":82}," application\u002Fx-www-form-urlencoded 表单\n",[72,6003,6004,6007,6009,6011,6013,6016],{"class":74,"line":107},[72,6005,6006],{"class":508},"    client",[72,6008,627],{"class":100},[72,6010,5396],{"class":89},[72,6012,476],{"class":446},[72,6014,6015],{"class":93},"\"https:\u002F\u002Fhttpbin.org\u002Fpost\"",[72,6017,1771],{"class":446},[72,6019,6020,6022,6025,6027,6029,6031,6034,6036,6039,6041,6043,6046,6048,6051],{"class":74,"line":122},[72,6021,1776],{"class":100},[72,6023,6024],{"class":89},"form",[72,6026,476],{"class":446},[72,6028,676],{"class":100},[72,6030,5537],{"class":446},[72,6032,6033],{"class":93},"\"username\"",[72,6035,453],{"class":446},[72,6037,6038],{"class":93}," \"admin\"",[72,6040,2399],{"class":446},[72,6042,3163],{"class":446},[72,6044,6045],{"class":93},"\"password\"",[72,6047,453],{"class":446},[72,6049,6050],{"class":93}," \"123456\"",[72,6052,6053],{"class":446},")])\n",[72,6055,6056,6058,6060,6062,6064],{"class":74,"line":260},[72,6057,1776],{"class":100},[72,6059,4445],{"class":89},[72,6061,575],{"class":446},[72,6063,3737],{"class":100},[72,6065,688],{"class":446},[72,6067,6068],{"class":74,"line":274},[72,6069,465],{"emptyLinePlaceholder":464},[72,6071,6072,6074],{"class":74,"line":287},[72,6073,596],{"class":78},[72,6075,6076],{"class":82}," multipart 文件上传\n",[72,6078,6079,6081,6084,6086,6088,6090,6093,6095,6098,6100,6103,6105,6107,6109,6111,6113,6116,6118,6120,6122,6124,6126],{"class":74,"line":301},[72,6080,605],{"class":100},[72,6082,6083],{"class":508}," part",[72,6085,611],{"class":100},[72,6087,129],{"class":440},[72,6089,443],{"class":100},[72,6091,6092],{"class":440},"multipart",[72,6094,443],{"class":100},[72,6096,6097],{"class":440},"Part",[72,6099,443],{"class":100},[72,6101,6102],{"class":89},"bytes",[72,6104,476],{"class":446},[72,6106,2856],{"class":440},[72,6108,443],{"class":100},[72,6110,1685],{"class":440},[72,6112,443],{"class":100},[72,6114,6115],{"class":89},"read",[72,6117,476],{"class":446},[72,6119,3727],{"class":93},[72,6121,682],{"class":446},[72,6123,627],{"class":100},[72,6125,1497],{"class":89},[72,6127,2501],{"class":446},[72,6129,6130,6132,6135,6137,6139],{"class":74,"line":317},[72,6131,1776],{"class":100},[72,6133,6134],{"class":89},"file_name",[72,6136,476],{"class":446},[72,6138,3727],{"class":93},[72,6140,766],{"class":446},[72,6142,6143,6145,6148,6150,6152,6154,6156,6158,6161,6163,6165],{"class":74,"line":331},[72,6144,605],{"class":100},[72,6146,6147],{"class":508}," form",[72,6149,611],{"class":100},[72,6151,129],{"class":440},[72,6153,443],{"class":100},[72,6155,6092],{"class":440},[72,6157,443],{"class":100},[72,6159,6160],{"class":440},"Form",[72,6162,443],{"class":100},[72,6164,3877],{"class":89},[72,6166,3984],{"class":446},[72,6168,6169,6171,6173,6175,6178,6180,6183],{"class":74,"line":353},[72,6170,1776],{"class":100},[72,6172,5031],{"class":89},[72,6174,476],{"class":446},[72,6176,6177],{"class":93},"\"desc\"",[72,6179,453],{"class":446},[72,6181,6182],{"class":93}," \"一个文件\"",[72,6184,1771],{"class":446},[72,6186,6187,6189,6192,6194,6197,6199,6201],{"class":74,"line":602},[72,6188,1776],{"class":100},[72,6190,6191],{"class":89},"part",[72,6193,476],{"class":446},[72,6195,6196],{"class":93},"\"file\"",[72,6198,453],{"class":446},[72,6200,6083],{"class":508},[72,6202,766],{"class":446},[72,6204,6205,6207,6209,6211,6213,6215,6217,6219,6221,6223,6225,6227,6229,6231,6233,6235],{"class":74,"line":657},[72,6206,6006],{"class":508},[72,6208,627],{"class":100},[72,6210,5396],{"class":89},[72,6212,476],{"class":446},[72,6214,6015],{"class":93},[72,6216,682],{"class":446},[72,6218,627],{"class":100},[72,6220,6092],{"class":89},[72,6222,476],{"class":446},[72,6224,6024],{"class":508},[72,6226,682],{"class":446},[72,6228,627],{"class":100},[72,6230,4445],{"class":89},[72,6232,575],{"class":446},[72,6234,3737],{"class":100},[72,6236,688],{"class":446},[72,6238,6239,6241],{"class":74,"line":691},[72,6240,852],{"class":440},[72,6242,855],{"class":446},[72,6244,6245],{"class":74,"line":720},[72,6246,560],{"class":446},[24,6248],{},[27,6250,6252],{"id":6251},"第-5-章-axumweb-服务端框架","第 5 章 axum：Web 服务端框架",[58,6254,6256],{"id":6255},"_51-它解决什么问题","5.1 它解决什么问题",[15,6258,6259,6260,6263],{},"axum 是 tokio 团队官方生态的 Web 框架，基于 tower 抽象，与 tokio\u002Fhyper 无缝配合。特点是",[19,6261,6262],{},"没有自己的宏魔法，路由和提取器全是类型系统","——编译器会拦住绝大多数错误。",[63,6265,6267],{"className":144,"code":6266,"language":146,"meta":68,"style":68},"[dependencies]\naxum = \"0.8\"\ntokio = { version = \"1\", features = [\"full\"] }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n",[53,6268,6269,6273,6278,6282,6286],{"__ignoreMap":68},[72,6270,6271],{"class":74,"line":75},[72,6272,153],{},[72,6274,6275],{"class":74,"line":86},[72,6276,6277],{},"axum = \"0.8\"\n",[72,6279,6280],{"class":74,"line":107},[72,6281,163],{},[72,6283,6284],{"class":74,"line":122},[72,6285,158],{},[72,6287,6288],{"class":74,"line":260},[72,6289,425],{},[12,6291,6292],{},[15,6293,6294,6295,6298,6299,6302],{},"注意：axum 0.8 起路径参数语法是 ",[53,6296,6297],{},"{id}","（旧版 ",[53,6300,6301],{},":id"," 已废弃）。",[58,6304,6306],{"id":6305},"_52-最小可运行服务","5.2 最小可运行服务",[63,6308,6310],{"className":428,"code":6309,"language":430,"meta":68,"style":68},"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",[53,6311,6312,6349,6367,6371,6383,6401,6405,6417,6434,6438,6473,6502,6528,6532,6577,6584,6621,6625,6629,6641,6653,6670,6695,6717,6739,6743,6750,6789,6800,6827],{"__ignoreMap":68},[72,6313,6314,6316,6319,6321,6323,6326,6328,6330,6332,6334,6336,6339,6342,6344,6347],{"class":74,"line":75},[72,6315,437],{"class":100},[72,6317,6318],{"class":440}," axum",[72,6320,443],{"class":100},[72,6322,447],{"class":446},[72,6324,6325],{"class":440},"routing",[72,6327,443],{"class":100},[72,6329,447],{"class":446},[72,6331,5012],{"class":473},[72,6333,453],{"class":446},[72,6335,5107],{"class":473},[72,6337,6338],{"class":446},"},",[72,6340,6341],{"class":440}," Router",[72,6343,453],{"class":446},[72,6345,6346],{"class":440}," Json",[72,6348,459],{"class":446},[72,6350,6351,6353,6355,6357,6359,6361,6363,6365],{"class":74,"line":86},[72,6352,437],{"class":100},[72,6354,97],{"class":440},[72,6356,443],{"class":100},[72,6358,447],{"class":446},[72,6360,450],{"class":440},[72,6362,453],{"class":446},[72,6364,456],{"class":440},[72,6366,459],{"class":446},[72,6368,6369],{"class":74,"line":107},[72,6370,465],{"emptyLinePlaceholder":464},[72,6372,6373,6375,6377,6379,6381],{"class":74,"line":122},[72,6374,470],{"class":446},[72,6376,177],{"class":473},[72,6378,476],{"class":446},[72,6380,479],{"class":440},[72,6382,492],{"class":446},[72,6384,6385,6387,6390,6392,6395,6397,6399],{"class":74,"line":260},[72,6386,497],{"class":100},[72,6388,6389],{"class":440}," Msg",[72,6391,616],{"class":446},[72,6393,6394],{"class":508}," message",[72,6396,512],{"class":100},[72,6398,515],{"class":440},[72,6400,1181],{"class":446},[72,6402,6403],{"class":74,"line":274},[72,6404,465],{"emptyLinePlaceholder":464},[72,6406,6407,6409,6411,6413,6415],{"class":74,"line":287},[72,6408,470],{"class":446},[72,6410,177],{"class":473},[72,6412,476],{"class":446},[72,6414,450],{"class":440},[72,6416,492],{"class":446},[72,6418,6419,6421,6424,6426,6428,6430,6432],{"class":74,"line":301},[72,6420,497],{"class":100},[72,6422,6423],{"class":440}," CreateUser",[72,6425,616],{"class":446},[72,6427,619],{"class":508},[72,6429,512],{"class":100},[72,6431,515],{"class":440},[72,6433,1181],{"class":446},[72,6435,6436],{"class":74,"line":317},[72,6437,465],{"emptyLinePlaceholder":464},[72,6439,6440,6442,6444,6447,6449,6451,6453,6455,6457,6459,6461,6464,6467,6470],{"class":74,"line":331},[72,6441,2791],{"class":100},[72,6443,2794],{"class":100},[72,6445,6446],{"class":89}," hello",[72,6448,575],{"class":446},[72,6450,578],{"class":100},[72,6452,1715],{"class":100},[72,6454,3441],{"class":446},[72,6456,3444],{"class":440},[72,6458,3447],{"class":440},[72,6460,616],{"class":446},[72,6462,6463],{"class":93}," \"Hello, world\"",[72,6465,6466],{"class":446}," }",[72,6468,6469],{"class":78},"       \u002F\u002F",[72,6471,6472],{"class":82}," 返回 &str 自动成为 text\u002Fplain\n",[72,6474,6475,6477,6479,6482,6484,6486,6488,6490,6493,6495,6497,6499],{"class":74,"line":353},[72,6476,2791],{"class":100},[72,6478,2794],{"class":100},[72,6480,6481],{"class":89}," hello_json",[72,6483,575],{"class":446},[72,6485,578],{"class":100},[72,6487,6346],{"class":440},[72,6489,543],{"class":446},[72,6491,6492],{"class":440},"Msg",[72,6494,1731],{"class":446},[72,6496,616],{"class":446},[72,6498,2358],{"class":78},[72,6500,6501],{"class":82}," 返回 Json\u003CT> 自动序列化 + 设 Content-Type\n",[72,6503,6504,6507,6509,6511,6513,6515,6517,6519,6521,6523,6525],{"class":74,"line":602},[72,6505,6506],{"class":89},"    Json",[72,6508,476],{"class":446},[72,6510,6492],{"class":440},[72,6512,616],{"class":446},[72,6514,6394],{"class":508},[72,6516,512],{"class":100},[72,6518,3732],{"class":93},[72,6520,627],{"class":100},[72,6522,630],{"class":89},[72,6524,575],{"class":446},[72,6526,6527],{"class":446}," })\n",[72,6529,6530],{"class":74,"line":657},[72,6531,560],{"class":446},[72,6533,6534,6536,6538,6541,6543,6546,6548,6551,6553,6555,6557,6559,6562,6565,6567,6569,6571,6573,6575],{"class":74,"line":691},[72,6535,2791],{"class":100},[72,6537,2794],{"class":100},[72,6539,6540],{"class":89}," create_user",[72,6542,476],{"class":446},[72,6544,6545],{"class":89},"Json",[72,6547,476],{"class":446},[72,6549,6550],{"class":508},"payload",[72,6552,682],{"class":446},[72,6554,512],{"class":100},[72,6556,6346],{"class":440},[72,6558,543],{"class":446},[72,6560,6561],{"class":440},"CreateUser",[72,6563,6564],{"class":446},">)",[72,6566,578],{"class":100},[72,6568,6346],{"class":440},[72,6570,543],{"class":446},[72,6572,6492],{"class":440},[72,6574,1731],{"class":446},[72,6576,503],{"class":446},[72,6578,6579,6581],{"class":74,"line":720},[72,6580,596],{"class":78},[72,6582,6583],{"class":82}," Json\u003CT> 既是提取器（请求）也是响应器：靠的是函数参数的「提取器模式」\n",[72,6585,6586,6588,6590,6592,6594,6596,6598,6600,6602,6605,6607,6609,6611,6613,6615,6617,6619],{"class":74,"line":725},[72,6587,6506],{"class":89},[72,6589,476],{"class":446},[72,6591,6492],{"class":440},[72,6593,616],{"class":446},[72,6595,6394],{"class":508},[72,6597,512],{"class":100},[72,6599,1787],{"class":89},[72,6601,476],{"class":446},[72,6603,6604],{"class":93},"\"创建用户 ",[72,6606,742],{"class":446},[72,6608,699],{"class":93},[72,6610,453],{"class":446},[72,6612,1388],{"class":508},[72,6614,627],{"class":100},[72,6616,1307],{"class":473},[72,6618,682],{"class":446},[72,6620,6527],{"class":446},[72,6622,6623],{"class":74,"line":733},[72,6624,560],{"class":446},[72,6626,6627],{"class":74,"line":769},[72,6628,465],{"emptyLinePlaceholder":464},[72,6630,6631,6633,6635,6637,6639],{"class":74,"line":774},[72,6632,470],{"class":446},[72,6634,2778],{"class":473},[72,6636,443],{"class":100},[72,6638,2783],{"class":473},[72,6640,2786],{"class":446},[72,6642,6643,6645,6647,6649,6651],{"class":74,"line":782},[72,6644,2791],{"class":100},[72,6646,2794],{"class":100},[72,6648,572],{"class":89},[72,6650,575],{"class":446},[72,6652,503],{"class":446},[72,6654,6655,6657,6660,6662,6664,6666,6668],{"class":74,"line":797},[72,6656,605],{"class":100},[72,6658,6659],{"class":508}," app",[72,6661,611],{"class":100},[72,6663,6341],{"class":440},[72,6665,443],{"class":100},[72,6667,3877],{"class":89},[72,6669,3984],{"class":446},[72,6671,6672,6674,6677,6679,6682,6684,6687,6689,6692],{"class":74,"line":829},[72,6673,1776],{"class":100},[72,6675,6676],{"class":89},"route",[72,6678,476],{"class":446},[72,6680,6681],{"class":93},"\"\u002F\"",[72,6683,453],{"class":446},[72,6685,6686],{"class":89}," get",[72,6688,476],{"class":446},[72,6690,6691],{"class":508},"hello",[72,6693,6694],{"class":446},"))\n",[72,6696,6697,6699,6701,6703,6706,6708,6710,6712,6715],{"class":74,"line":849},[72,6698,1776],{"class":100},[72,6700,6676],{"class":89},[72,6702,476],{"class":446},[72,6704,6705],{"class":93},"\"\u002Fjson\"",[72,6707,453],{"class":446},[72,6709,6686],{"class":89},[72,6711,476],{"class":446},[72,6713,6714],{"class":508},"hello_json",[72,6716,6694],{"class":446},[72,6718,6719,6721,6723,6725,6728,6730,6732,6734,6737],{"class":74,"line":858},[72,6720,1776],{"class":100},[72,6722,6676],{"class":89},[72,6724,476],{"class":446},[72,6726,6727],{"class":93},"\"\u002Fusers\"",[72,6729,453],{"class":446},[72,6731,5107],{"class":89},[72,6733,476],{"class":446},[72,6735,6736],{"class":508},"create_user",[72,6738,4147],{"class":446},[72,6740,6741],{"class":74,"line":2023},[72,6742,465],{"emptyLinePlaceholder":464},[72,6744,6745,6747],{"class":74,"line":2028},[72,6746,596],{"class":78},[72,6748,6749],{"class":82}," 0.8 写法：TcpListener + serve\n",[72,6751,6752,6754,6757,6759,6761,6763,6766,6768,6771,6773,6776,6778,6781,6783,6785,6787],{"class":74,"line":2036},[72,6753,605],{"class":100},[72,6755,6756],{"class":508}," listener",[72,6758,611],{"class":100},[72,6760,114],{"class":440},[72,6762,443],{"class":100},[72,6764,6765],{"class":440},"net",[72,6767,443],{"class":100},[72,6769,6770],{"class":440},"TcpListener",[72,6772,443],{"class":100},[72,6774,6775],{"class":89},"bind",[72,6777,476],{"class":446},[72,6779,6780],{"class":93},"\"0.0.0.0:3000\"",[72,6782,682],{"class":446},[72,6784,2916],{"class":100},[72,6786,1497],{"class":89},[72,6788,1500],{"class":446},[72,6790,6791,6793,6795,6798],{"class":74,"line":2069},[72,6792,694],{"class":89},[72,6794,476],{"class":446},[72,6796,6797],{"class":93},"\"监听 http:\u002F\u002Flocalhost:3000\"",[72,6799,766],{"class":446},[72,6801,6802,6805,6807,6810,6812,6815,6817,6819,6821,6823,6825],{"class":74,"line":2096},[72,6803,6804],{"class":440},"    axum",[72,6806,443],{"class":100},[72,6808,6809],{"class":89},"serve",[72,6811,476],{"class":446},[72,6813,6814],{"class":508},"listener",[72,6816,453],{"class":446},[72,6818,6659],{"class":508},[72,6820,682],{"class":446},[72,6822,2916],{"class":100},[72,6824,1497],{"class":89},[72,6826,1500],{"class":446},[72,6828,6829],{"class":74,"line":2132},[72,6830,560],{"class":446},[15,6832,6833,6836,6837,6840],{},[19,6834,6835],{},"为什么 handler 是普通 async fn 而不是 trait？"," axum 的提取器模式（handler 参数实现 ",[53,6838,6839],{},"FromRequest"," 即可）让 handler 签名本身就是 API 文档：参数声明了需要什么，返回类型声明了产出什么，全部编译期检查。请求缺字段、类型不符，根本到不了你的业务代码。",[58,6842,6844],{"id":6843},"_53-路径参数查询参数header共享状态","5.3 路径参数、查询参数、Header、共享状态",[63,6846,6848],{"className":428,"code":6847,"language":430,"meta":68,"style":68},"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",[53,6849,6850,6861,6886,6905,6924,6928,6946,6993,6997,7004,7017,7026,7041,7052,7056,7060,7072,7110,7114,7169,7173,7211,7269,7273,7277,7325,7368,7390,7433,7476,7480,7484,7491,7544,7563,7582,7592,7621,7625,7629,7633,7645,7658,7671,7700,7718,7724,7729,7746,7782,7804,7827,7850,7870,7875,7910,7935],{"__ignoreMap":68},[72,6851,6852,6854,6856,6858],{"class":74,"line":75},[72,6853,437],{"class":100},[72,6855,6318],{"class":440},[72,6857,443],{"class":100},[72,6859,6860],{"class":446},"{\n",[72,6862,6863,6866,6868,6870,6873,6875,6878,6880,6883],{"class":74,"line":86},[72,6864,6865],{"class":440},"    extract",[72,6867,443],{"class":100},[72,6869,447],{"class":446},[72,6871,6872],{"class":440},"Path",[72,6874,453],{"class":446},[72,6876,6877],{"class":440}," Query",[72,6879,453],{"class":446},[72,6881,6882],{"class":440}," State",[72,6884,6885],{"class":446},"},\n",[72,6887,6888,6891,6893,6895,6898,6900,6903],{"class":74,"line":107},[72,6889,6890],{"class":440},"    http",[72,6892,443],{"class":100},[72,6894,447],{"class":446},[72,6896,6897],{"class":440},"HeaderMap",[72,6899,453],{"class":446},[72,6901,6902],{"class":440}," StatusCode",[72,6904,6885],{"class":446},[72,6906,6907,6909,6911,6913,6915,6918,6920,6922],{"class":74,"line":122},[72,6908,6506],{"class":440},[72,6910,453],{"class":446},[72,6912,6341],{"class":440},[72,6914,453],{"class":446},[72,6916,6917],{"class":440}," routing",[72,6919,443],{"class":100},[72,6921,5012],{"class":473},[72,6923,518],{"class":446},[72,6925,6926],{"class":74,"line":260},[72,6927,459],{"class":446},[72,6929,6930,6932,6934,6936,6938,6940,6942,6944],{"class":74,"line":274},[72,6931,437],{"class":100},[72,6933,97],{"class":440},[72,6935,443],{"class":100},[72,6937,447],{"class":446},[72,6939,450],{"class":440},[72,6941,453],{"class":446},[72,6943,456],{"class":440},[72,6945,459],{"class":446},[72,6947,6948,6950,6952,6954,6956,6958,6960,6962,6964,6967,6969,6971,6973,6975,6978,6980,6982,6985,6987,6990],{"class":74,"line":287},[72,6949,437],{"class":100},[72,6951,1121],{"class":440},[72,6953,443],{"class":100},[72,6955,447],{"class":446},[72,6957,1126],{"class":440},[72,6959,443],{"class":100},[72,6961,1131],{"class":440},[72,6963,453],{"class":446},[72,6965,6966],{"class":440}," sync",[72,6968,443],{"class":100},[72,6970,447],{"class":446},[72,6972,4033],{"class":440},[72,6974,453],{"class":446},[72,6976,6977],{"class":440}," atomic",[72,6979,443],{"class":100},[72,6981,447],{"class":446},[72,6983,6984],{"class":440},"AtomicU64",[72,6986,453],{"class":446},[72,6988,6989],{"class":440}," Ordering",[72,6991,6992],{"class":446},"}}};\n",[72,6994,6995],{"class":74,"line":301},[72,6996,465],{"emptyLinePlaceholder":464},[72,6998,6999,7001],{"class":74,"line":317},[72,7000,999],{"class":78},[72,7002,7003],{"class":82}," 共享状态：Arc 包一层。原子计数器之外的可变数据用 Mutex\u002FRwLock\n",[72,7005,7006,7008,7010,7012,7015],{"class":74,"line":331},[72,7007,470],{"class":446},[72,7009,177],{"class":473},[72,7011,476],{"class":446},[72,7013,7014],{"class":440},"Clone",[72,7016,492],{"class":446},[72,7018,7019,7021,7024],{"class":74,"line":353},[72,7020,497],{"class":100},[72,7022,7023],{"class":440}," AppState",[72,7025,503],{"class":446},[72,7027,7028,7031,7033,7035,7037,7039],{"class":74,"line":602},[72,7029,7030],{"class":508},"    visit_count",[72,7032,512],{"class":100},[72,7034,4126],{"class":440},[72,7036,543],{"class":446},[72,7038,6984],{"class":440},[72,7040,974],{"class":446},[72,7042,7043,7046,7048,7050],{"class":74,"line":657},[72,7044,7045],{"class":508},"    app_name",[72,7047,512],{"class":100},[72,7049,515],{"class":440},[72,7051,518],{"class":446},[72,7053,7054],{"class":74,"line":691},[72,7055,560],{"class":446},[72,7057,7058],{"class":74,"line":720},[72,7059,465],{"emptyLinePlaceholder":464},[72,7061,7062,7064,7066,7068,7070],{"class":74,"line":725},[72,7063,470],{"class":446},[72,7065,177],{"class":473},[72,7067,476],{"class":446},[72,7069,450],{"class":440},[72,7071,492],{"class":446},[72,7073,7074,7076,7079,7081,7084,7086,7088,7090,7093,7095,7098,7100,7102,7104,7106,7108],{"class":74,"line":733},[72,7075,497],{"class":100},[72,7077,7078],{"class":440}," PageQuery",[72,7080,616],{"class":446},[72,7082,7083],{"class":508}," page",[72,7085,512],{"class":100},[72,7087,540],{"class":440},[72,7089,543],{"class":446},[72,7091,7092],{"class":440},"u32",[72,7094,549],{"class":446},[72,7096,7097],{"class":508}," size",[72,7099,512],{"class":100},[72,7101,540],{"class":440},[72,7103,543],{"class":446},[72,7105,7092],{"class":440},[72,7107,1731],{"class":446},[72,7109,1181],{"class":446},[72,7111,7112],{"class":74,"line":769},[72,7113,465],{"emptyLinePlaceholder":464},[72,7115,7116,7118,7120,7123,7125,7127,7129,7131,7133,7135,7138,7140,7142,7144,7146,7148,7150,7152,7154,7157,7159,7161,7163,7165,7167],{"class":74,"line":774},[72,7117,2791],{"class":100},[72,7119,2794],{"class":100},[72,7121,7122],{"class":89}," get_user",[72,7124,476],{"class":446},[72,7126,6872],{"class":89},[72,7128,476],{"class":446},[72,7130,2425],{"class":508},[72,7132,682],{"class":446},[72,7134,512],{"class":100},[72,7136,7137],{"class":440}," Path",[72,7139,543],{"class":446},[72,7141,2267],{"class":440},[72,7143,6564],{"class":446},[72,7145,578],{"class":100},[72,7147,515],{"class":440},[72,7149,616],{"class":446},[72,7151,1787],{"class":89},[72,7153,476],{"class":446},[72,7155,7156],{"class":93},"\"用户 ",[72,7158,447],{"class":446},[72,7160,2425],{"class":93},[72,7162,707],{"class":446},[72,7164,699],{"class":93},[72,7166,682],{"class":446},[72,7168,1181],{"class":446},[72,7170,7171],{"class":74,"line":782},[72,7172,465],{"emptyLinePlaceholder":464},[72,7174,7175,7177,7179,7182,7184,7187,7189,7192,7194,7196,7198,7200,7203,7205,7207,7209],{"class":74,"line":797},[72,7176,2791],{"class":100},[72,7178,2794],{"class":100},[72,7180,7181],{"class":89}," list_users",[72,7183,476],{"class":446},[72,7185,7186],{"class":89},"Query",[72,7188,476],{"class":446},[72,7190,7191],{"class":508},"q",[72,7193,682],{"class":446},[72,7195,512],{"class":100},[72,7197,6877],{"class":440},[72,7199,543],{"class":446},[72,7201,7202],{"class":440},"PageQuery",[72,7204,6564],{"class":446},[72,7206,578],{"class":100},[72,7208,515],{"class":440},[72,7210,503],{"class":446},[72,7212,7213,7216,7218,7221,7223,7226,7228,7230,7232,7235,7237,7240,7242,7245,7247,7249,7251,7253,7255,7258,7260,7262,7264,7267],{"class":74,"line":829},[72,7214,7215],{"class":89},"    format!",[72,7217,476],{"class":446},[72,7219,7220],{"class":93},"\"第 ",[72,7222,742],{"class":446},[72,7224,7225],{"class":93}," 页，每页 ",[72,7227,742],{"class":446},[72,7229,5655],{"class":93},[72,7231,453],{"class":446},[72,7233,7234],{"class":508}," q",[72,7236,627],{"class":100},[72,7238,7239],{"class":473},"page",[72,7241,627],{"class":100},[72,7243,7244],{"class":89},"unwrap_or",[72,7246,476],{"class":446},[72,7248,2062],{"class":89},[72,7250,2399],{"class":446},[72,7252,7234],{"class":508},[72,7254,627],{"class":100},[72,7256,7257],{"class":473},"size",[72,7259,627],{"class":100},[72,7261,7244],{"class":89},[72,7263,476],{"class":446},[72,7265,7266],{"class":89},"20",[72,7268,6694],{"class":446},[72,7270,7271],{"class":74,"line":849},[72,7272,560],{"class":446},[72,7274,7275],{"class":74,"line":858},[72,7276,465],{"emptyLinePlaceholder":464},[72,7278,7279,7281,7283,7286,7288,7291,7293,7296,7298,7300,7302,7304,7307,7309,7312,7314,7317,7319,7321,7323],{"class":74,"line":2023},[72,7280,2791],{"class":100},[72,7282,2794],{"class":100},[72,7284,7285],{"class":89}," visit",[72,7287,476],{"class":446},[72,7289,7290],{"class":89},"State",[72,7292,476],{"class":446},[72,7294,7295],{"class":508},"state",[72,7297,682],{"class":446},[72,7299,512],{"class":100},[72,7301,6882],{"class":440},[72,7303,543],{"class":446},[72,7305,7306],{"class":440},"AppState",[72,7308,549],{"class":446},[72,7310,7311],{"class":508}," headers",[72,7313,512],{"class":100},[72,7315,7316],{"class":440}," HeaderMap",[72,7318,682],{"class":446},[72,7320,578],{"class":100},[72,7322,515],{"class":440},[72,7324,503],{"class":446},[72,7326,7327,7329,7331,7333,7336,7338,7341,7343,7346,7348,7350,7352,7354,7356,7359,7361,7364,7366],{"class":74,"line":2028},[72,7328,605],{"class":100},[72,7330,4239],{"class":508},[72,7332,611],{"class":100},[72,7334,7335],{"class":508}," state",[72,7337,627],{"class":100},[72,7339,7340],{"class":473},"visit_count",[72,7342,627],{"class":100},[72,7344,7345],{"class":89},"fetch_add",[72,7347,476],{"class":446},[72,7349,2062],{"class":89},[72,7351,453],{"class":446},[72,7353,6989],{"class":440},[72,7355,443],{"class":100},[72,7357,7358],{"class":440},"Relaxed",[72,7360,682],{"class":446},[72,7362,7363],{"class":100}," +",[72,7365,4273],{"class":89},[72,7367,688],{"class":446},[72,7369,7370,7372,7375,7377,7379,7381,7383,7385,7388],{"class":74,"line":2036},[72,7371,605],{"class":100},[72,7373,7374],{"class":508}," ua",[72,7376,611],{"class":100},[72,7378,7311],{"class":508},[72,7380,627],{"class":100},[72,7382,5012],{"class":89},[72,7384,476],{"class":446},[72,7386,7387],{"class":93},"\"user-agent\"",[72,7389,1771],{"class":446},[72,7391,7392,7394,7397,7399,7402,7404,7406,7408,7410,7413,7415,7417,7420,7422,7424,7426,7428,7431],{"class":74,"line":2069},[72,7393,1776],{"class":100},[72,7395,7396],{"class":89},"and_then",[72,7398,476],{"class":446},[72,7400,7401],{"class":100},"|",[72,7403,3556],{"class":508},[72,7405,7401],{"class":100},[72,7407,802],{"class":508},[72,7409,627],{"class":100},[72,7411,7412],{"class":89},"to_str",[72,7414,575],{"class":446},[72,7416,627],{"class":100},[72,7418,7419],{"class":89},"ok",[72,7421,3045],{"class":446},[72,7423,627],{"class":100},[72,7425,7244],{"class":89},[72,7427,476],{"class":446},[72,7429,7430],{"class":93},"\"unknown\"",[72,7432,766],{"class":446},[72,7434,7435,7437,7439,7442,7444,7447,7449,7451,7453,7456,7458,7461,7463,7465,7467,7469,7471,7474],{"class":74,"line":2096},[72,7436,7215],{"class":89},[72,7438,476],{"class":446},[72,7440,7441],{"class":93},"\"[",[72,7443,742],{"class":446},[72,7445,7446],{"class":93},"] 第 ",[72,7448,447],{"class":446},[72,7450,4267],{"class":93},[72,7452,707],{"class":446},[72,7454,7455],{"class":93}," 次访问，UA: ",[72,7457,447],{"class":446},[72,7459,7460],{"class":93},"ua",[72,7462,707],{"class":446},[72,7464,699],{"class":93},[72,7466,453],{"class":446},[72,7468,7335],{"class":508},[72,7470,627],{"class":100},[72,7472,7473],{"class":473},"app_name",[72,7475,1771],{"class":446},[72,7477,7478],{"class":74,"line":2132},[72,7479,560],{"class":446},[72,7481,7482],{"class":74,"line":2137},[72,7483,465],{"emptyLinePlaceholder":464},[72,7485,7486,7488],{"class":74,"line":2144},[72,7487,999],{"class":78},[72,7489,7490],{"class":82}," 返回 (StatusCode, Json\u003CT>) 自定义状态码；impl IntoResponse 可以返回任意可转响应的类型\n",[72,7492,7493,7495,7497,7500,7502,7504,7506,7508,7510,7512,7514,7516,7518,7520,7522,7524,7526,7528,7530,7532,7534,7536,7538,7540,7542],{"class":74,"line":4627},[72,7494,2791],{"class":100},[72,7496,2794],{"class":100},[72,7498,7499],{"class":89}," maybe_found",[72,7501,476],{"class":446},[72,7503,7290],{"class":89},[72,7505,476],{"class":446},[72,7507,704],{"class":508},[72,7509,682],{"class":446},[72,7511,512],{"class":100},[72,7513,6882],{"class":440},[72,7515,543],{"class":446},[72,7517,7306],{"class":440},[72,7519,6564],{"class":446},[72,7521,578],{"class":100},[72,7523,1662],{"class":440},[72,7525,543],{"class":446},[72,7527,6545],{"class":440},[72,7529,543],{"class":446},[72,7531,5613],{"class":508},[72,7533,443],{"class":100},[72,7535,1144],{"class":440},[72,7537,549],{"class":446},[72,7539,6902],{"class":440},[72,7541,1731],{"class":446},[72,7543,503],{"class":446},[72,7545,7546,7548,7550,7552,7554,7556,7559,7561],{"class":74,"line":4650},[72,7547,1895],{"class":100},[72,7549,662],{"class":508},[72,7551,627],{"class":100},[72,7553,7473],{"class":473},[72,7555,627],{"class":100},[72,7557,7558],{"class":89},"is_empty",[72,7560,575],{"class":446},[72,7562,503],{"class":446},[72,7564,7565,7567,7569,7572,7575,7577,7579],{"class":74,"line":4655},[72,7566,2547],{"class":440},[72,7568,476],{"class":446},[72,7570,7571],{"class":440},"StatusCode",[72,7573,7574],{"class":100},"::NOT_FOUND",[72,7576,682],{"class":446},[72,7578,552],{"class":78},[72,7580,7581],{"class":82}," 返回 Err(StatusCode) 直接成为对应状态码的空响应\n",[72,7583,7584,7587,7590],{"class":74,"line":4663},[72,7585,7586],{"class":446},"    }",[72,7588,7589],{"class":100}," else",[72,7591,503],{"class":446},[72,7593,7594,7596,7598,7600,7602,7604,7606,7608,7611,7614,7616,7618],{"class":74,"line":4699},[72,7595,2635],{"class":440},[72,7597,476],{"class":446},[72,7599,6545],{"class":89},[72,7601,476],{"class":446},[72,7603,5613],{"class":440},[72,7605,443],{"class":100},[72,7607,1397],{"class":89},[72,7609,7610],{"class":446},"({",[72,7612,7613],{"class":93},"\"ok\"",[72,7615,512],{"class":100},[72,7617,1434],{"class":89},[72,7619,7620],{"class":446},"})))\n",[72,7622,7623],{"class":74,"line":4724},[72,7624,1936],{"class":446},[72,7626,7627],{"class":74,"line":4742},[72,7628,560],{"class":446},[72,7630,7631],{"class":74,"line":4764},[72,7632,465],{"emptyLinePlaceholder":464},[72,7634,7635,7637,7639,7641,7643],{"class":74,"line":4788},[72,7636,470],{"class":446},[72,7638,2778],{"class":473},[72,7640,443],{"class":100},[72,7642,2783],{"class":473},[72,7644,2786],{"class":446},[72,7646,7648,7650,7652,7654,7656],{"class":74,"line":7647},42,[72,7649,2791],{"class":100},[72,7651,2794],{"class":100},[72,7653,572],{"class":89},[72,7655,575],{"class":446},[72,7657,503],{"class":446},[72,7659,7661,7663,7665,7667,7669],{"class":74,"line":7660},43,[72,7662,605],{"class":100},[72,7664,7335],{"class":508},[72,7666,611],{"class":100},[72,7668,7023],{"class":440},[72,7670,503],{"class":446},[72,7672,7674,7677,7679,7681,7683,7685,7687,7689,7691,7693,7695,7697],{"class":74,"line":7673},44,[72,7675,7676],{"class":508},"        visit_count",[72,7678,512],{"class":100},[72,7680,4126],{"class":440},[72,7682,443],{"class":100},[72,7684,3877],{"class":89},[72,7686,476],{"class":446},[72,7688,6984],{"class":440},[72,7690,443],{"class":100},[72,7692,3877],{"class":89},[72,7694,476],{"class":446},[72,7696,1330],{"class":89},[72,7698,7699],{"class":446},")),\n",[72,7701,7703,7706,7708,7711,7713,7715],{"class":74,"line":7702},45,[72,7704,7705],{"class":508},"        app_name",[72,7707,512],{"class":100},[72,7709,7710],{"class":93}," \"demo\"",[72,7712,627],{"class":100},[72,7714,630],{"class":89},[72,7716,7717],{"class":446},"(),\n",[72,7719,7721],{"class":74,"line":7720},46,[72,7722,7723],{"class":446},"    };\n",[72,7725,7727],{"class":74,"line":7726},47,[72,7728,465],{"emptyLinePlaceholder":464},[72,7730,7732,7734,7736,7738,7740,7742,7744],{"class":74,"line":7731},48,[72,7733,605],{"class":100},[72,7735,6659],{"class":508},[72,7737,611],{"class":100},[72,7739,6341],{"class":440},[72,7741,443],{"class":100},[72,7743,3877],{"class":89},[72,7745,3984],{"class":446},[72,7747,7749,7751,7753,7755,7758,7760,7762,7764,7766,7768,7770,7772,7775,7777,7779],{"class":74,"line":7748},49,[72,7750,1776],{"class":100},[72,7752,6676],{"class":89},[72,7754,476],{"class":446},[72,7756,7757],{"class":93},"\"\u002Fusers\u002F",[72,7759,447],{"class":446},[72,7761,2425],{"class":93},[72,7763,707],{"class":446},[72,7765,699],{"class":93},[72,7767,453],{"class":446},[72,7769,6686],{"class":89},[72,7771,476],{"class":446},[72,7773,7774],{"class":508},"get_user",[72,7776,1804],{"class":446},[72,7778,1033],{"class":78},[72,7780,7781],{"class":82}," {id} 与 Path\u003Cu64> 对应，类型不符自动 400\n",[72,7783,7785,7787,7789,7791,7793,7795,7797,7799,7802],{"class":74,"line":7784},50,[72,7786,1776],{"class":100},[72,7788,6676],{"class":89},[72,7790,476],{"class":446},[72,7792,6727],{"class":93},[72,7794,453],{"class":446},[72,7796,6686],{"class":89},[72,7798,476],{"class":446},[72,7800,7801],{"class":508},"list_users",[72,7803,6694],{"class":446},[72,7805,7807,7809,7811,7813,7816,7818,7820,7822,7825],{"class":74,"line":7806},51,[72,7808,1776],{"class":100},[72,7810,6676],{"class":89},[72,7812,476],{"class":446},[72,7814,7815],{"class":93},"\"\u002Fvisit\"",[72,7817,453],{"class":446},[72,7819,6686],{"class":89},[72,7821,476],{"class":446},[72,7823,7824],{"class":508},"visit",[72,7826,6694],{"class":446},[72,7828,7830,7832,7834,7836,7839,7841,7843,7845,7848],{"class":74,"line":7829},52,[72,7831,1776],{"class":100},[72,7833,6676],{"class":89},[72,7835,476],{"class":446},[72,7837,7838],{"class":93},"\"\u002Ffound\"",[72,7840,453],{"class":446},[72,7842,6686],{"class":89},[72,7844,476],{"class":446},[72,7846,7847],{"class":508},"maybe_found",[72,7849,6694],{"class":446},[72,7851,7853,7855,7858,7860,7862,7864,7867],{"class":74,"line":7852},53,[72,7854,1776],{"class":100},[72,7856,7857],{"class":89},"with_state",[72,7859,476],{"class":446},[72,7861,7295],{"class":508},[72,7863,712],{"class":446},[72,7865,7866],{"class":78},"                            \u002F\u002F",[72,7868,7869],{"class":82}," 注入状态，handler 用 State 提取\n",[72,7871,7873],{"class":74,"line":7872},54,[72,7874,465],{"emptyLinePlaceholder":464},[72,7876,7878,7880,7882,7884,7886,7888,7890,7892,7894,7896,7898,7900,7902,7904,7906,7908],{"class":74,"line":7877},55,[72,7879,605],{"class":100},[72,7881,6756],{"class":508},[72,7883,611],{"class":100},[72,7885,114],{"class":440},[72,7887,443],{"class":100},[72,7889,6765],{"class":440},[72,7891,443],{"class":100},[72,7893,6770],{"class":440},[72,7895,443],{"class":100},[72,7897,6775],{"class":89},[72,7899,476],{"class":446},[72,7901,6780],{"class":93},[72,7903,682],{"class":446},[72,7905,2916],{"class":100},[72,7907,1497],{"class":89},[72,7909,1500],{"class":446},[72,7911,7913,7915,7917,7919,7921,7923,7925,7927,7929,7931,7933],{"class":74,"line":7912},56,[72,7914,6804],{"class":440},[72,7916,443],{"class":100},[72,7918,6809],{"class":89},[72,7920,476],{"class":446},[72,7922,6814],{"class":508},[72,7924,453],{"class":446},[72,7926,6659],{"class":508},[72,7928,682],{"class":446},[72,7930,2916],{"class":100},[72,7932,1497],{"class":89},[72,7934,1500],{"class":446},[72,7936,7938],{"class":74,"line":7937},57,[72,7939,560],{"class":446},[58,7941,7943],{"id":7942},"_54-统一错误响应与中间件","5.4 统一错误响应与中间件",[63,7945,7947],{"className":428,"code":7946,"language":430,"meta":68,"style":68},"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",[53,7948,7949,7959,7969,7988,8011,8015,8027,8031,8038,8045,8079,8083,8098,8119,8144,8175,8194,8220,8241,8249,8254,8259,8293,8297,8301,8305,8312,8335,8377,8381,8385,8412,8438,8442,8446,8453,8503,8530,8555,8584,8614,8669,8674,8678,8682,8694,8706,8722,8743,8772,8809,8813,8847,8871],{"__ignoreMap":68},[72,7950,7951,7953,7955,7957],{"class":74,"line":75},[72,7952,437],{"class":100},[72,7954,6318],{"class":440},[72,7956,443],{"class":100},[72,7958,6860],{"class":446},[72,7960,7961,7963,7965,7967],{"class":74,"line":86},[72,7962,6890],{"class":440},[72,7964,443],{"class":100},[72,7966,7571],{"class":440},[72,7968,518],{"class":446},[72,7970,7971,7974,7976,7978,7981,7983,7986],{"class":74,"line":107},[72,7972,7973],{"class":440},"    response",[72,7975,443],{"class":100},[72,7977,447],{"class":446},[72,7979,7980],{"class":440},"IntoResponse",[72,7982,453],{"class":446},[72,7984,7985],{"class":440}," Response",[72,7987,6885],{"class":446},[72,7989,7990,7992,7994,7996,7998,8000,8002,8004,8006,8009],{"class":74,"line":122},[72,7991,6506],{"class":440},[72,7993,453],{"class":446},[72,7995,6341],{"class":440},[72,7997,453],{"class":446},[72,7999,6917],{"class":440},[72,8001,443],{"class":100},[72,8003,5012],{"class":473},[72,8005,453],{"class":446},[72,8007,8008],{"class":473}," middleware",[72,8010,518],{"class":446},[72,8012,8013],{"class":74,"line":260},[72,8014,459],{"class":446},[72,8016,8017,8019,8021,8023,8025],{"class":74,"line":274},[72,8018,437],{"class":100},[72,8020,581],{"class":440},[72,8022,443],{"class":100},[72,8024,5137],{"class":473},[72,8026,688],{"class":446},[72,8028,8029],{"class":74,"line":287},[72,8030,465],{"emptyLinePlaceholder":464},[72,8032,8033,8035],{"class":74,"line":301},[72,8034,999],{"class":78},[72,8036,8037],{"class":82}," 应用错误类型：实现 IntoResponse 后，handler 直接返回 Result\u003CT, AppError>\n",[72,8039,8040,8042],{"class":74,"line":317},[72,8041,999],{"class":78},[72,8043,8044],{"class":82}," 为什么？否则每个 handler 都要手写 try\u002Fmatch 转 HTTP 响应，业务代码被噪音淹没\n",[72,8046,8047,8050,8053,8055,8058,8060,8062,8064,8067,8069,8071,8073,8075,8077],{"class":74,"line":331},[72,8048,8049],{"class":100},"enum",[72,8051,8052],{"class":440}," AppError",[72,8054,616],{"class":446},[72,8056,8057],{"class":89}," NotFound",[72,8059,476],{"class":446},[72,8061,546],{"class":440},[72,8063,2399],{"class":446},[72,8065,8066],{"class":89}," Internal",[72,8068,476],{"class":446},[72,8070,1601],{"class":440},[72,8072,443],{"class":100},[72,8074,2193],{"class":440},[72,8076,682],{"class":446},[72,8078,1181],{"class":446},[72,8080,8081],{"class":74,"line":353},[72,8082,465],{"emptyLinePlaceholder":464},[72,8084,8085,8088,8091,8094,8096],{"class":74,"line":602},[72,8086,8087],{"class":100},"impl",[72,8089,8090],{"class":440}," IntoResponse",[72,8092,8093],{"class":100}," for",[72,8095,8052],{"class":440},[72,8097,503],{"class":446},[72,8099,8100,8103,8106,8108,8111,8113,8115,8117],{"class":74,"line":657},[72,8101,8102],{"class":100},"    fn",[72,8104,8105],{"class":89}," into_response",[72,8107,476],{"class":446},[72,8109,8110],{"class":508},"self",[72,8112,682],{"class":446},[72,8114,578],{"class":100},[72,8116,7985],{"class":440},[72,8118,503],{"class":446},[72,8120,8121,8123,8125,8127,8129,8132,8134,8136,8139,8142],{"class":74,"line":691},[72,8122,4188],{"class":100},[72,8124,3163],{"class":446},[72,8126,5488],{"class":508},[72,8128,453],{"class":446},[72,8130,8131],{"class":508}," msg",[72,8133,682],{"class":446},[72,8135,611],{"class":100},[72,8137,8138],{"class":100}," match",[72,8140,8141],{"class":508}," self",[72,8143,503],{"class":446},[72,8145,8146,8149,8151,8153,8155,8158,8160,8162,8164,8166,8168,8170,8173],{"class":74,"line":720},[72,8147,8148],{"class":440},"            AppError",[72,8150,443],{"class":100},[72,8152,2476],{"class":89},[72,8154,476],{"class":446},[72,8156,8157],{"class":508},"m",[72,8159,682],{"class":446},[72,8161,2564],{"class":100},[72,8163,3163],{"class":446},[72,8165,7571],{"class":440},[72,8167,7574],{"class":100},[72,8169,453],{"class":446},[72,8171,8172],{"class":508}," m",[72,8174,2270],{"class":446},[72,8176,8177,8179,8181,8184,8186,8188,8190,8192],{"class":74,"line":725},[72,8178,8148],{"class":440},[72,8180,443],{"class":100},[72,8182,8183],{"class":89},"Internal",[72,8185,476],{"class":446},[72,8187,2048],{"class":508},[72,8189,682],{"class":446},[72,8191,2564],{"class":100},[72,8193,503],{"class":446},[72,8195,8196,8199,8201,8204,8206,8209,8211,8213,8215,8217],{"class":74,"line":733},[72,8197,8198],{"class":89},"                eprintln!",[72,8200,476],{"class":446},[72,8202,8203],{"class":93},"\"内部错误: ",[72,8205,447],{"class":446},[72,8207,8208],{"class":93},"e:#",[72,8210,707],{"class":446},[72,8212,699],{"class":93},[72,8214,712],{"class":446},[72,8216,552],{"class":78},[72,8218,8219],{"class":82}," 详细错误只进服务端日志\n",[72,8221,8222,8225,8227,8230,8232,8235,8237,8239],{"class":74,"line":769},[72,8223,8224],{"class":446},"                (",[72,8226,7571],{"class":440},[72,8228,8229],{"class":100},"::INTERNAL_SERVER_ERROR",[72,8231,453],{"class":446},[72,8233,8234],{"class":93}," \"服务器内部错误\"",[72,8236,627],{"class":100},[72,8238,630],{"class":89},[72,8240,2501],{"class":446},[72,8242,8243,8246],{"class":74,"line":774},[72,8244,8245],{"class":78},"                \u002F\u002F",[72,8247,8248],{"class":82}," 为什么不把内部错误详情返回给客户端？——防止泄露路径、SQL 等敏感信息\n",[72,8250,8251],{"class":74,"line":782},[72,8252,8253],{"class":446},"            }\n",[72,8255,8256],{"class":74,"line":797},[72,8257,8258],{"class":446},"        };\n",[72,8260,8261,8264,8266,8268,8270,8272,8274,8276,8279,8281,8283,8286,8288,8291],{"class":74,"line":829},[72,8262,8263],{"class":446},"        (",[72,8265,5488],{"class":508},[72,8267,453],{"class":446},[72,8269,6346],{"class":89},[72,8271,476],{"class":446},[72,8273,1397],{"class":89},[72,8275,7610],{"class":446},[72,8277,8278],{"class":93}," \"error\"",[72,8280,512],{"class":100},[72,8282,8131],{"class":508},[72,8284,8285],{"class":446}," })))",[72,8287,627],{"class":100},[72,8289,8290],{"class":89},"into_response",[72,8292,3984],{"class":446},[72,8294,8295],{"class":74,"line":849},[72,8296,1936],{"class":446},[72,8298,8299],{"class":74,"line":858},[72,8300,560],{"class":446},[72,8302,8303],{"class":74,"line":2023},[72,8304,465],{"emptyLinePlaceholder":464},[72,8306,8307,8309],{"class":74,"line":2028},[72,8308,999],{"class":78},[72,8310,8311],{"class":82}," From 转换让 ? 直接可用\n",[72,8313,8314,8316,8319,8321,8323,8325,8327,8329,8331,8333],{"class":74,"line":2036},[72,8315,8087],{"class":100},[72,8317,8318],{"class":440}," From",[72,8320,543],{"class":446},[72,8322,1601],{"class":508},[72,8324,443],{"class":100},[72,8326,2193],{"class":440},[72,8328,1731],{"class":446},[72,8330,8093],{"class":100},[72,8332,8052],{"class":440},[72,8334,503],{"class":446},[72,8336,8337,8339,8342,8344,8346,8348,8350,8352,8354,8356,8358,8361,8363,8365,8367,8369,8371,8373,8375],{"class":74,"line":2069},[72,8338,8102],{"class":100},[72,8340,8341],{"class":89}," from",[72,8343,476],{"class":446},[72,8345,2048],{"class":508},[72,8347,512],{"class":100},[72,8349,1650],{"class":440},[72,8351,443],{"class":100},[72,8353,2193],{"class":440},[72,8355,682],{"class":446},[72,8357,578],{"class":100},[72,8359,8360],{"class":508}," Self",[72,8362,616],{"class":446},[72,8364,8052],{"class":440},[72,8366,443],{"class":100},[72,8368,8183],{"class":89},[72,8370,476],{"class":446},[72,8372,2048],{"class":508},[72,8374,682],{"class":446},[72,8376,1181],{"class":446},[72,8378,8379],{"class":74,"line":2096},[72,8380,560],{"class":446},[72,8382,8383],{"class":74,"line":2132},[72,8384,465],{"emptyLinePlaceholder":464},[72,8386,8387,8389,8391,8394,8396,8398,8400,8402,8404,8406,8408,8410],{"class":74,"line":2137},[72,8388,2791],{"class":100},[72,8390,2794],{"class":100},[72,8392,8393],{"class":89}," handler",[72,8395,575],{"class":446},[72,8397,578],{"class":100},[72,8399,1662],{"class":440},[72,8401,543],{"class":446},[72,8403,546],{"class":440},[72,8405,453],{"class":446},[72,8407,8052],{"class":440},[72,8409,1731],{"class":446},[72,8411,503],{"class":446},[72,8413,8414,8417,8419,8422,8424,8426,8428,8431,8433,8435],{"class":74,"line":2144},[72,8415,8416],{"class":440},"    Err",[72,8418,476],{"class":446},[72,8420,8421],{"class":440},"AppError",[72,8423,443],{"class":100},[72,8425,2476],{"class":89},[72,8427,476],{"class":446},[72,8429,8430],{"class":93},"\"东西不存在\"",[72,8432,627],{"class":100},[72,8434,630],{"class":89},[72,8436,8437],{"class":446},"()))\n",[72,8439,8440],{"class":74,"line":4627},[72,8441,560],{"class":446},[72,8443,8444],{"class":74,"line":4650},[72,8445,465],{"emptyLinePlaceholder":464},[72,8447,8448,8450],{"class":74,"line":4655},[72,8449,999],{"class":78},[72,8451,8452],{"class":82}," 简单中间件：函数式 middleware\n",[72,8454,8455,8457,8459,8462,8464,8467,8469,8471,8473,8476,8478,8481,8483,8486,8488,8490,8492,8495,8497,8499,8501],{"class":74,"line":4663},[72,8456,2791],{"class":100},[72,8458,2794],{"class":100},[72,8460,8461],{"class":89}," log_requests",[72,8463,476],{"class":446},[72,8465,8466],{"class":508},"req",[72,8468,512],{"class":100},[72,8470,6318],{"class":440},[72,8472,443],{"class":100},[72,8474,8475],{"class":440},"extract",[72,8477,443],{"class":100},[72,8479,8480],{"class":440},"Request",[72,8482,453],{"class":446},[72,8484,8485],{"class":508}," next",[72,8487,512],{"class":100},[72,8489,8008],{"class":440},[72,8491,443],{"class":100},[72,8493,8494],{"class":440},"Next",[72,8496,682],{"class":446},[72,8498,578],{"class":100},[72,8500,7985],{"class":440},[72,8502,503],{"class":446},[72,8504,8505,8507,8510,8512,8514,8516,8518,8520,8523,8525,8528],{"class":74,"line":4699},[72,8506,605],{"class":100},[72,8508,8509],{"class":508}," start",[72,8511,611],{"class":100},[72,8513,1121],{"class":440},[72,8515,443],{"class":100},[72,8517,2846],{"class":440},[72,8519,443],{"class":100},[72,8521,8522],{"class":440},"Instant",[72,8524,443],{"class":100},[72,8526,8527],{"class":89},"now",[72,8529,1500],{"class":446},[72,8531,8532,8534,8537,8539,8542,8544,8547,8549,8551,8553],{"class":74,"line":4724},[72,8533,605],{"class":100},[72,8535,8536],{"class":508}," method",[72,8538,611],{"class":100},[72,8540,8541],{"class":508}," req",[72,8543,627],{"class":100},[72,8545,8546],{"class":89},"method",[72,8548,575],{"class":446},[72,8550,627],{"class":100},[72,8552,4200],{"class":89},[72,8554,1500],{"class":446},[72,8556,8557,8559,8561,8563,8565,8567,8570,8572,8574,8576,8578,8580,8582],{"class":74,"line":4742},[72,8558,605],{"class":100},[72,8560,5765],{"class":508},[72,8562,611],{"class":100},[72,8564,8541],{"class":508},[72,8566,627],{"class":100},[72,8568,8569],{"class":89},"uri",[72,8571,575],{"class":446},[72,8573,627],{"class":100},[72,8575,1710],{"class":89},[72,8577,575],{"class":446},[72,8579,627],{"class":100},[72,8581,671],{"class":89},[72,8583,1500],{"class":446},[72,8585,8586,8588,8590,8592,8594,8596,8599,8601,8603,8605,8607,8609,8611],{"class":74,"line":4764},[72,8587,605],{"class":100},[72,8589,5384],{"class":508},[72,8591,611],{"class":100},[72,8593,8485],{"class":508},[72,8595,627],{"class":100},[72,8597,8598],{"class":89},"run",[72,8600,476],{"class":446},[72,8602,8466],{"class":508},[72,8604,682],{"class":446},[72,8606,2879],{"class":100},[72,8608,3030],{"class":446},[72,8610,1033],{"class":78},[72,8612,8613],{"class":82}," 调用链路的下一层\n",[72,8615,8616,8618,8620,8622,8624,8626,8628,8630,8632,8634,8637,8639,8641,8643,8645,8647,8650,8652,8654,8656,8658,8660,8662,8664,8667],{"class":74,"line":4788},[72,8617,694],{"class":89},[72,8619,476],{"class":446},[72,8621,699],{"class":93},[72,8623,447],{"class":446},[72,8625,8546],{"class":93},[72,8627,707],{"class":446},[72,8629,616],{"class":446},[72,8631,1710],{"class":93},[72,8633,707],{"class":446},[72,8635,8636],{"class":93}," -> ",[72,8638,742],{"class":446},[72,8640,3163],{"class":93},[72,8642,447],{"class":446},[72,8644,2108],{"class":93},[72,8646,707],{"class":446},[72,8648,8649],{"class":93},")\"",[72,8651,453],{"class":446},[72,8653,5384],{"class":508},[72,8655,627],{"class":100},[72,8657,5488],{"class":89},[72,8659,633],{"class":446},[72,8661,8509],{"class":508},[72,8663,627],{"class":100},[72,8665,8666],{"class":89},"elapsed",[72,8668,2020],{"class":446},[72,8670,8671],{"class":74,"line":7647},[72,8672,8673],{"class":508},"    resp\n",[72,8675,8676],{"class":74,"line":7660},[72,8677,560],{"class":446},[72,8679,8680],{"class":74,"line":7673},[72,8681,465],{"emptyLinePlaceholder":464},[72,8683,8684,8686,8688,8690,8692],{"class":74,"line":7702},[72,8685,470],{"class":446},[72,8687,2778],{"class":473},[72,8689,443],{"class":100},[72,8691,2783],{"class":473},[72,8693,2786],{"class":446},[72,8695,8696,8698,8700,8702,8704],{"class":74,"line":7720},[72,8697,2791],{"class":100},[72,8699,2794],{"class":100},[72,8701,572],{"class":89},[72,8703,575],{"class":446},[72,8705,503],{"class":446},[72,8707,8708,8710,8712,8714,8716,8718,8720],{"class":74,"line":7726},[72,8709,605],{"class":100},[72,8711,6659],{"class":508},[72,8713,611],{"class":100},[72,8715,6341],{"class":440},[72,8717,443],{"class":100},[72,8719,3877],{"class":89},[72,8721,3984],{"class":446},[72,8723,8724,8726,8728,8730,8732,8734,8736,8738,8741],{"class":74,"line":7731},[72,8725,1776],{"class":100},[72,8727,6676],{"class":89},[72,8729,476],{"class":446},[72,8731,6681],{"class":93},[72,8733,453],{"class":446},[72,8735,6686],{"class":89},[72,8737,476],{"class":446},[72,8739,8740],{"class":508},"handler",[72,8742,6694],{"class":446},[72,8744,8745,8747,8750,8752,8755,8757,8760,8762,8765,8767,8769],{"class":74,"line":7748},[72,8746,1776],{"class":100},[72,8748,8749],{"class":89},"layer",[72,8751,476],{"class":446},[72,8753,8754],{"class":440},"middleware",[72,8756,443],{"class":100},[72,8758,8759],{"class":89},"from_fn",[72,8761,476],{"class":446},[72,8763,8764],{"class":508},"log_requests",[72,8766,1804],{"class":446},[72,8768,552],{"class":78},[72,8770,8771],{"class":82}," 挂在整个 Router 上\n",[72,8773,8774,8776,8779,8781,8783,8786,8788,8790,8792,8794,8796,8799,8801,8804,8806],{"class":74,"line":7784},[72,8775,1776],{"class":100},[72,8777,8778],{"class":89},"fallback",[72,8780,476],{"class":446},[72,8782,1784],{"class":100},[72,8784,8785],{"class":100}," async",[72,8787,616],{"class":446},[72,8789,3163],{"class":446},[72,8791,7571],{"class":440},[72,8793,7574],{"class":100},[72,8795,453],{"class":446},[72,8797,8798],{"class":93}," \"路由不存在\"",[72,8800,682],{"class":446},[72,8802,8803],{"class":446}," });",[72,8805,552],{"class":78},[72,8807,8808],{"class":82}," 自定义 404\n",[72,8810,8811],{"class":74,"line":7806},[72,8812,465],{"emptyLinePlaceholder":464},[72,8814,8815,8817,8819,8821,8823,8825,8827,8829,8831,8833,8835,8837,8839,8841,8843,8845],{"class":74,"line":7829},[72,8816,605],{"class":100},[72,8818,6756],{"class":508},[72,8820,611],{"class":100},[72,8822,114],{"class":440},[72,8824,443],{"class":100},[72,8826,6765],{"class":440},[72,8828,443],{"class":100},[72,8830,6770],{"class":440},[72,8832,443],{"class":100},[72,8834,6775],{"class":89},[72,8836,476],{"class":446},[72,8838,6780],{"class":93},[72,8840,682],{"class":446},[72,8842,2916],{"class":100},[72,8844,1497],{"class":89},[72,8846,1500],{"class":446},[72,8848,8849,8851,8853,8855,8857,8859,8861,8863,8865,8867,8869],{"class":74,"line":7852},[72,8850,6804],{"class":440},[72,8852,443],{"class":100},[72,8854,6809],{"class":89},[72,8856,476],{"class":446},[72,8858,6814],{"class":508},[72,8860,453],{"class":446},[72,8862,6659],{"class":508},[72,8864,682],{"class":446},[72,8866,2916],{"class":100},[72,8868,1497],{"class":89},[72,8870,1500],{"class":446},[72,8872,8873],{"class":74,"line":7872},[72,8874,560],{"class":446},[58,8876,8878],{"id":8877},"_55-路由嵌套与静态文件","5.5 路由嵌套与静态文件",[63,8880,8882],{"className":428,"code":8881,"language":430,"meta":68,"style":68},"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",[53,8883,8884,8907,8911,8926,8964,8968,8972,8986,8996,9020,9027],{"__ignoreMap":68},[72,8885,8886,8888,8890,8892,8894,8897,8899,8901,8903,8905],{"class":74,"line":75},[72,8887,437],{"class":100},[72,8889,6318],{"class":440},[72,8891,443],{"class":100},[72,8893,447],{"class":446},[72,8895,8896],{"class":440},"Router",[72,8898,453],{"class":446},[72,8900,6917],{"class":440},[72,8902,443],{"class":100},[72,8904,5012],{"class":473},[72,8906,459],{"class":446},[72,8908,8909],{"class":74,"line":86},[72,8910,465],{"emptyLinePlaceholder":464},[72,8912,8913,8915,8918,8920,8922,8924],{"class":74,"line":107},[72,8914,569],{"class":100},[72,8916,8917],{"class":89}," api_routes",[72,8919,575],{"class":446},[72,8921,578],{"class":100},[72,8923,6341],{"class":440},[72,8925,503],{"class":446},[72,8927,8928,8931,8933,8935,8937,8939,8941,8943,8946,8948,8950,8952,8954,8956,8958,8961],{"class":74,"line":122},[72,8929,8930],{"class":440},"    Router",[72,8932,443],{"class":100},[72,8934,3877],{"class":89},[72,8936,575],{"class":446},[72,8938,627],{"class":100},[72,8940,6676],{"class":89},[72,8942,476],{"class":446},[72,8944,8945],{"class":93},"\"\u002Fhealth\"",[72,8947,453],{"class":446},[72,8949,6686],{"class":89},[72,8951,476],{"class":446},[72,8953,1784],{"class":100},[72,8955,8785],{"class":100},[72,8957,616],{"class":446},[72,8959,8960],{"class":93}," \"ok\"",[72,8962,8963],{"class":446}," }))\n",[72,8965,8966],{"class":74,"line":260},[72,8967,560],{"class":446},[72,8969,8970],{"class":74,"line":274},[72,8971,465],{"emptyLinePlaceholder":464},[72,8973,8974,8976,8978,8980,8982,8984],{"class":74,"line":287},[72,8975,569],{"class":100},[72,8977,6659],{"class":89},[72,8979,575],{"class":446},[72,8981,578],{"class":100},[72,8983,6341],{"class":440},[72,8985,503],{"class":446},[72,8987,8988,8990,8992,8994],{"class":74,"line":301},[72,8989,8930],{"class":440},[72,8991,443],{"class":100},[72,8993,3877],{"class":89},[72,8995,3984],{"class":446},[72,8997,8998,9000,9003,9005,9008,9010,9012,9014,9017],{"class":74,"line":317},[72,8999,1776],{"class":100},[72,9001,9002],{"class":89},"nest",[72,9004,476],{"class":446},[72,9006,9007],{"class":93},"\"\u002Fapi\u002Fv1\"",[72,9009,453],{"class":446},[72,9011,8917],{"class":89},[72,9013,3045],{"class":446},[72,9015,9016],{"class":78},"      \u002F\u002F",[72,9018,9019],{"class":82}," 嵌套：\u002Fapi\u002Fv1\u002Fhealth\n",[72,9021,9022,9024],{"class":74,"line":331},[72,9023,2826],{"class":78},[72,9025,9026],{"class":82}," .nest_service(\"\u002Fstatic\", tower_http::services::ServeDir::new(\"static\")) \u002F\u002F 静态文件需 tower-http\n",[72,9028,9029],{"class":74,"line":353},[72,9030,560],{"class":446},[24,9032],{},[27,9034,9036],{"id":9035},"第-6-章-clap命令行参数解析","第 6 章 clap：命令行参数解析",[58,9038,9040],{"id":9039},"_61-它解决什么问题","6.1 它解决什么问题",[15,9042,9043,9044,9047,9048,9051,9052,9055],{},"解析 ",[53,9045,9046],{},"mytool --input a.txt -v"," 这类参数。clap 的 ",[19,9049,9050],{},"derive 模式","让你用结构体定义 CLI，自动生成 ",[53,9053,9054],{},"--help","、自动校验、自动类型转换。",[63,9057,9059],{"className":144,"code":9058,"language":146,"meta":68,"style":68},"[dependencies]\nclap = { version = \"4\", features = [\"derive\"] }\n",[53,9060,9061,9065],{"__ignoreMap":68},[72,9062,9063],{"class":74,"line":75},[72,9064,153],{},[72,9066,9067],{"class":74,"line":86},[72,9068,9069],{},"clap = { version = \"4\", features = [\"derive\"] }\n",[15,9071,9072,9075],{},[19,9073,9074],{},"为什么用 derive 而不是 builder？"," 结构体即文档：参数长什么样、什么类型、什么默认值，一眼看清，且解析结果就是强类型结构体，不存在「字符串忘了 parse」的 bug。",[58,9077,9079],{"id":9078},"_62-完整案例","6.2 完整案例",[63,9081,9083],{"className":428,"code":9082,"language":430,"meta":68,"style":68},"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",[53,9084,9085,9111,9115,9127,9170,9179,9187,9206,9217,9221,9228,9259,9270,9274,9281,9316,9327,9331,9338,9403,9414,9418,9425,9456,9467,9471,9484,9496,9500,9504,9520,9542,9546,9559,9567,9574,9581,9589,9606,9618,9623,9630,9637,9648,9652,9656,9660,9670,9693,9697,9710,9731,9775,9814,9818,9854,9858],{"__ignoreMap":68},[72,9086,9087,9089,9092,9094,9096,9099,9101,9104,9106,9109],{"class":74,"line":75},[72,9088,437],{"class":100},[72,9090,9091],{"class":440}," clap",[72,9093,443],{"class":100},[72,9095,447],{"class":446},[72,9097,9098],{"class":440},"Parser",[72,9100,453],{"class":446},[72,9102,9103],{"class":440}," Subcommand",[72,9105,453],{"class":446},[72,9107,9108],{"class":440}," ValueEnum",[72,9110,459],{"class":446},[72,9112,9113],{"class":74,"line":86},[72,9114,465],{"emptyLinePlaceholder":464},[72,9116,9117,9119,9121,9123,9125],{"class":74,"line":107},[72,9118,470],{"class":446},[72,9120,177],{"class":473},[72,9122,476],{"class":446},[72,9124,9098],{"class":440},[72,9126,492],{"class":446},[72,9128,9129,9131,9134,9136,9139,9141,9144,9146,9149,9151,9154,9156,9159,9161,9164,9166,9168],{"class":74,"line":122},[72,9130,470],{"class":446},[72,9132,9133],{"class":473},"command",[72,9135,476],{"class":446},[72,9137,9138],{"class":473},"name ",[72,9140,910],{"class":100},[72,9142,9143],{"class":93}," \"mytool\"",[72,9145,453],{"class":446},[72,9147,9148],{"class":473}," version",[72,9150,453],{"class":446},[72,9152,9153],{"class":473}," about ",[72,9155,910],{"class":100},[72,9157,9158],{"class":93}," \"一个示例工具\"",[72,9160,453],{"class":446},[72,9162,9163],{"class":473}," long_about ",[72,9165,910],{"class":100},[72,9167,651],{"class":440},[72,9169,492],{"class":446},[72,9171,9172,9174,9177],{"class":74,"line":260},[72,9173,497],{"class":100},[72,9175,9176],{"class":440}," Cli",[72,9178,503],{"class":446},[72,9180,9181,9184],{"class":74,"line":274},[72,9182,9183],{"class":78},"    \u002F\u002F\u002F",[72,9185,9186],{"class":82}," 输入文件（doc comment 自动成为帮助文本！）\n",[72,9188,9189,9191,9194,9196,9199,9201,9204],{"class":74,"line":287},[72,9190,899],{"class":446},[72,9192,9193],{"class":473},"arg",[72,9195,476],{"class":446},[72,9197,9198],{"class":473},"short",[72,9200,453],{"class":446},[72,9202,9203],{"class":473}," long",[72,9205,492],{"class":446},[72,9207,9208,9211,9213,9215],{"class":74,"line":301},[72,9209,9210],{"class":508},"    input",[72,9212,512],{"class":100},[72,9214,515],{"class":440},[72,9216,518],{"class":446},[72,9218,9219],{"class":74,"line":317},[72,9220,465],{"emptyLinePlaceholder":464},[72,9222,9223,9225],{"class":74,"line":331},[72,9224,9183],{"class":78},[72,9226,9227],{"class":82}," 输出目录\n",[72,9229,9230,9232,9234,9236,9238,9240,9242,9244,9247,9249,9252,9254,9256],{"class":74,"line":353},[72,9231,899],{"class":446},[72,9233,9193],{"class":473},[72,9235,476],{"class":446},[72,9237,9198],{"class":473},[72,9239,453],{"class":446},[72,9241,9203],{"class":473},[72,9243,453],{"class":446},[72,9245,9246],{"class":473}," default_value ",[72,9248,910],{"class":100},[72,9250,9251],{"class":93}," \".\u002Fout\"",[72,9253,916],{"class":446},[72,9255,1887],{"class":78},[72,9257,9258],{"class":82}," 不传时的默认值\n",[72,9260,9261,9264,9266,9268],{"class":74,"line":602},[72,9262,9263],{"class":508},"    output",[72,9265,512],{"class":100},[72,9267,515],{"class":440},[72,9269,518],{"class":446},[72,9271,9272],{"class":74,"line":657},[72,9273,465],{"emptyLinePlaceholder":464},[72,9275,9276,9278],{"class":74,"line":691},[72,9277,9183],{"class":78},[72,9279,9280],{"class":82}," 详细日志，-v \u002F -vv \u002F -vvv 可叠加\n",[72,9282,9283,9285,9287,9289,9291,9293,9295,9297,9300,9302,9304,9306,9309,9311,9314],{"class":74,"line":720},[72,9284,899],{"class":446},[72,9286,9193],{"class":473},[72,9288,476],{"class":446},[72,9290,9198],{"class":473},[72,9292,453],{"class":446},[72,9294,9203],{"class":473},[72,9296,453],{"class":446},[72,9298,9299],{"class":473}," action ",[72,9301,910],{"class":100},[72,9303,9091],{"class":473},[72,9305,443],{"class":100},[72,9307,9308],{"class":440},"ArgAction",[72,9310,443],{"class":100},[72,9312,9313],{"class":440},"Count",[72,9315,492],{"class":446},[72,9317,9318,9321,9323,9325],{"class":74,"line":725},[72,9319,9320],{"class":508},"    verbose",[72,9322,512],{"class":100},[72,9324,528],{"class":440},[72,9326,518],{"class":446},[72,9328,9329],{"class":74,"line":733},[72,9330,465],{"emptyLinePlaceholder":464},[72,9332,9333,9335],{"class":74,"line":769},[72,9334,9183],{"class":78},[72,9336,9337],{"class":82}," 并发数，1-32\n",[72,9339,9340,9342,9344,9346,9348,9350,9352,9354,9357,9359,9362,9364,9367,9369,9371,9373,9376,9379,9381,9383,9385,9387,9390,9392,9394,9397,9400],{"class":74,"line":774},[72,9341,899],{"class":446},[72,9343,9193],{"class":473},[72,9345,476],{"class":446},[72,9347,9198],{"class":473},[72,9349,453],{"class":446},[72,9351,9203],{"class":473},[72,9353,453],{"class":446},[72,9355,9356],{"class":473}," default_value_t ",[72,9358,910],{"class":100},[72,9360,9361],{"class":473}," 4",[72,9363,453],{"class":446},[72,9365,9366],{"class":473}," value_parser ",[72,9368,910],{"class":100},[72,9370,9091],{"class":473},[72,9372,443],{"class":100},[72,9374,9375],{"class":473},"value_parser",[72,9377,9378],{"class":100},"!",[72,9380,476],{"class":446},[72,9382,4783],{"class":440},[72,9384,682],{"class":446},[72,9386,627],{"class":100},[72,9388,9389],{"class":473},"range",[72,9391,476],{"class":446},[72,9393,2062],{"class":473},[72,9395,9396],{"class":100},"..=",[72,9398,9399],{"class":473},"32",[72,9401,9402],{"class":446},"))]\n",[72,9404,9405,9408,9410,9412],{"class":74,"line":782},[72,9406,9407],{"class":508},"    jobs",[72,9409,512],{"class":100},[72,9411,528],{"class":440},[72,9413,518],{"class":446},[72,9415,9416],{"class":74,"line":797},[72,9417,465],{"emptyLinePlaceholder":464},[72,9419,9420,9422],{"class":74,"line":829},[72,9421,9183],{"class":78},[72,9423,9424],{"class":82}," 输出格式\n",[72,9426,9427,9429,9431,9433,9436,9438,9441,9443,9445,9447,9450,9452,9454],{"class":74,"line":849},[72,9428,899],{"class":446},[72,9430,9193],{"class":473},[72,9432,476],{"class":446},[72,9434,9435],{"class":473},"long",[72,9437,453],{"class":446},[72,9439,9440],{"class":473}," value_enum",[72,9442,453],{"class":446},[72,9444,9356],{"class":473},[72,9446,910],{"class":100},[72,9448,9449],{"class":440}," Format",[72,9451,443],{"class":100},[72,9453,6545],{"class":440},[72,9455,492],{"class":446},[72,9457,9458,9461,9463,9465],{"class":74,"line":858},[72,9459,9460],{"class":508},"    format",[72,9462,512],{"class":100},[72,9464,9449],{"class":440},[72,9466,518],{"class":446},[72,9468,9469],{"class":74,"line":2023},[72,9470,465],{"emptyLinePlaceholder":464},[72,9472,9473,9475,9477,9479,9482],{"class":74,"line":2028},[72,9474,899],{"class":446},[72,9476,9133],{"class":473},[72,9478,476],{"class":446},[72,9480,9481],{"class":473},"subcommand",[72,9483,492],{"class":446},[72,9485,9486,9489,9491,9494],{"class":74,"line":2036},[72,9487,9488],{"class":508},"    command",[72,9490,512],{"class":100},[72,9492,9493],{"class":440}," Commands",[72,9495,518],{"class":446},[72,9497,9498],{"class":74,"line":2069},[72,9499,560],{"class":446},[72,9501,9502],{"class":74,"line":2096},[72,9503,465],{"emptyLinePlaceholder":464},[72,9505,9506,9508,9510,9512,9514,9516,9518],{"class":74,"line":2132},[72,9507,470],{"class":446},[72,9509,177],{"class":473},[72,9511,476],{"class":446},[72,9513,7014],{"class":440},[72,9515,453],{"class":446},[72,9517,9108],{"class":440},[72,9519,492],{"class":446},[72,9521,9522,9524,9526,9528,9530,9532,9535,9537,9540],{"class":74,"line":2137},[72,9523,8049],{"class":100},[72,9525,9449],{"class":440},[72,9527,616],{"class":446},[72,9529,6346],{"class":440},[72,9531,453],{"class":446},[72,9533,9534],{"class":440}," Text",[72,9536,453],{"class":446},[72,9538,9539],{"class":440}," Csv",[72,9541,1181],{"class":446},[72,9543,9544],{"class":74,"line":2144},[72,9545,465],{"emptyLinePlaceholder":464},[72,9547,9548,9550,9552,9554,9557],{"class":74,"line":4627},[72,9549,470],{"class":446},[72,9551,177],{"class":473},[72,9553,476],{"class":446},[72,9555,9556],{"class":440},"Subcommand",[72,9558,492],{"class":446},[72,9560,9561,9563,9565],{"class":74,"line":4650},[72,9562,8049],{"class":100},[72,9564,9493],{"class":440},[72,9566,503],{"class":446},[72,9568,9569,9571],{"class":74,"line":4655},[72,9570,9183],{"class":78},[72,9572,9573],{"class":82}," 处理文件\n",[72,9575,9576,9579],{"class":74,"line":4663},[72,9577,9578],{"class":440},"    Process",[72,9580,503],{"class":446},[72,9582,9583,9586],{"class":74,"line":4699},[72,9584,9585],{"class":78},"        \u002F\u002F\u002F",[72,9587,9588],{"class":82}," 是否强制覆盖\n",[72,9590,9591,9594,9596,9598,9600,9602,9604],{"class":74,"line":4724},[72,9592,9593],{"class":446},"        #[",[72,9595,9193],{"class":473},[72,9597,476],{"class":446},[72,9599,9198],{"class":473},[72,9601,453],{"class":446},[72,9603,9203],{"class":473},[72,9605,492],{"class":446},[72,9607,9608,9611,9613,9616],{"class":74,"line":4742},[72,9609,9610],{"class":508},"        force",[72,9612,512],{"class":100},[72,9614,9615],{"class":440}," bool",[72,9617,518],{"class":446},[72,9619,9620],{"class":74,"line":4764},[72,9621,9622],{"class":446},"    },\n",[72,9624,9625,9627],{"class":74,"line":4788},[72,9626,9183],{"class":78},[72,9628,9629],{"class":82}," 清理缓存（位置参数：直接写在命令后面，不需要 --）\n",[72,9631,9632,9635],{"class":74,"line":7647},[72,9633,9634],{"class":440},"    Clean",[72,9636,503],{"class":446},[72,9638,9639,9642,9644,9646],{"class":74,"line":7660},[72,9640,9641],{"class":508},"        target",[72,9643,512],{"class":100},[72,9645,515],{"class":440},[72,9647,518],{"class":446},[72,9649,9650],{"class":74,"line":7673},[72,9651,9622],{"class":446},[72,9653,9654],{"class":74,"line":7702},[72,9655,560],{"class":446},[72,9657,9658],{"class":74,"line":7720},[72,9659,465],{"emptyLinePlaceholder":464},[72,9661,9662,9664,9666,9668],{"class":74,"line":7726},[72,9663,569],{"class":100},[72,9665,572],{"class":89},[72,9667,575],{"class":446},[72,9669,503],{"class":446},[72,9671,9672,9674,9677,9679,9681,9683,9686,9688,9690],{"class":74,"line":7731},[72,9673,605],{"class":100},[72,9675,9676],{"class":508}," cli",[72,9678,611],{"class":100},[72,9680,9176],{"class":440},[72,9682,443],{"class":100},[72,9684,9685],{"class":89},"parse",[72,9687,4306],{"class":446},[72,9689,552],{"class":78},[72,9691,9692],{"class":82}," 解析失败（缺参数\u002F类型错）自动打印错误和用法，退出码 2\n",[72,9694,9695],{"class":74,"line":7748},[72,9696,465],{"emptyLinePlaceholder":464},[72,9698,9699,9701,9703,9705,9708],{"class":74,"line":7784},[72,9700,2532],{"class":100},[72,9702,9676],{"class":508},[72,9704,627],{"class":100},[72,9706,9707],{"class":473},"command ",[72,9709,6860],{"class":446},[72,9711,9712,9715,9717,9720,9722,9725,9727,9729],{"class":74,"line":7806},[72,9713,9714],{"class":440},"        Commands",[72,9716,443],{"class":100},[72,9718,9719],{"class":440},"Process",[72,9721,616],{"class":446},[72,9723,9724],{"class":508}," force",[72,9726,6466],{"class":446},[72,9728,2564],{"class":100},[72,9730,503],{"class":446},[72,9732,9733,9736,9738,9741,9743,9745,9747,9750,9752,9755,9757,9760,9762,9765,9767,9769,9771,9773],{"class":74,"line":7829},[72,9734,9735],{"class":89},"            println!",[72,9737,476],{"class":446},[72,9739,9740],{"class":93},"\"处理 ",[72,9742,742],{"class":446},[72,9744,8636],{"class":93},[72,9746,742],{"class":446},[72,9748,9749],{"class":93}," (force=",[72,9751,447],{"class":446},[72,9753,9754],{"class":93},"force",[72,9756,707],{"class":446},[72,9758,9759],{"class":93},", jobs=",[72,9761,742],{"class":446},[72,9763,9764],{"class":93},", format=",[72,9766,447],{"class":446},[72,9768,2108],{"class":93},[72,9770,707],{"class":446},[72,9772,8649],{"class":93},[72,9774,518],{"class":446},[72,9776,9777,9780,9782,9785,9787,9789,9791,9794,9796,9798,9800,9803,9805,9807,9809,9812],{"class":74,"line":7852},[72,9778,9779],{"class":508},"                     cli",[72,9781,627],{"class":100},[72,9783,9784],{"class":473},"input",[72,9786,453],{"class":446},[72,9788,9676],{"class":508},[72,9790,627],{"class":100},[72,9792,9793],{"class":473},"output",[72,9795,453],{"class":446},[72,9797,9676],{"class":508},[72,9799,627],{"class":100},[72,9801,9802],{"class":473},"jobs",[72,9804,453],{"class":446},[72,9806,9676],{"class":508},[72,9808,627],{"class":100},[72,9810,9811],{"class":473},"format",[72,9813,766],{"class":446},[72,9815,9816],{"class":74,"line":7872},[72,9817,4477],{"class":446},[72,9819,9820,9822,9824,9827,9829,9832,9834,9836,9838,9840,9843,9845,9848,9850,9852],{"class":74,"line":7877},[72,9821,9714],{"class":440},[72,9823,443],{"class":100},[72,9825,9826],{"class":440},"Clean",[72,9828,616],{"class":446},[72,9830,9831],{"class":508}," target",[72,9833,6466],{"class":446},[72,9835,2564],{"class":100},[72,9837,2567],{"class":89},[72,9839,476],{"class":446},[72,9841,9842],{"class":93},"\"清理 ",[72,9844,447],{"class":446},[72,9846,9847],{"class":93},"target",[72,9849,707],{"class":446},[72,9851,699],{"class":93},[72,9853,2270],{"class":446},[72,9855,9856],{"class":74,"line":7912},[72,9857,1936],{"class":446},[72,9859,9860],{"class":74,"line":7937},[72,9861,560],{"class":446},[15,9863,9864],{},"运行效果：",[63,9866,9868],{"className":65,"code":9867,"language":67,"meta":68,"style":68},"$ mytool --help          # 自动生成的漂亮帮助\n$ mytool -i a.txt process --force\n$ mytool -i a.txt clean cache_dir\n",[53,9869,9870,9887,9905],{"__ignoreMap":68},[72,9871,9872,9875,9878,9881,9884],{"class":74,"line":75},[72,9873,9874],{"class":89},"$",[72,9876,9877],{"class":93}," mytool",[72,9879,9880],{"class":100}," --help",[72,9882,9883],{"class":78},"          #",[72,9885,9886],{"class":82}," 自动生成的漂亮帮助\n",[72,9888,9889,9891,9893,9896,9899,9902],{"class":74,"line":86},[72,9890,9874],{"class":89},[72,9892,9877],{"class":93},[72,9894,9895],{"class":100}," -i",[72,9897,9898],{"class":93}," a.txt",[72,9900,9901],{"class":93}," process",[72,9903,9904],{"class":100}," --force\n",[72,9906,9907,9909,9911,9913,9915,9918],{"class":74,"line":107},[72,9908,9874],{"class":89},[72,9910,9877],{"class":93},[72,9912,9895],{"class":100},[72,9914,9898],{"class":93},[72,9916,9917],{"class":93}," clean",[72,9919,9920],{"class":93}," cache_dir\n",[58,9922,9924],{"id":9923},"_63-常用-arg-属性速查","6.3 常用 arg 属性速查",[1566,9926,9927,9940],{},[1569,9928,9929],{},[1572,9930,9931,9934,9937],{},[1575,9932,9933],{},"属性",[1575,9935,9936],{},"作用",[1575,9938,9939],{},"例",[1585,9941,9942,9962,9979,9992,10009,10024,10037,10060],{},[1572,9943,9944,9949,9957],{},[1590,9945,9946],{},[53,9947,9948],{},"short, long",[1590,9950,9951,2688,9954],{},[53,9952,9953],{},"-x",[53,9955,9956],{},"--xxx",[1590,9958,9959],{},[53,9960,9961],{},"#[arg(short, long)]",[1572,9963,9964,9969,9972],{},[1590,9965,9966],{},[53,9967,9968],{},"default_value = \"...\"",[1590,9970,9971],{},"字符串默认值",[1590,9973,9974,9975,9978],{},"适合所有 ",[53,9976,9977],{},"FromStr"," 类型",[1572,9980,9981,9986,9989],{},[1590,9982,9983],{},[53,9984,9985],{},"default_value_t = 4",[1590,9987,9988],{},"类型化默认值",[1590,9990,9991],{},"数字、枚举用它可以出现在 help 里",[1572,9993,9994,9999,10006],{},[1590,9995,9996],{},[53,9997,9998],{},"env = \"API_KEY\"",[1590,10000,10001,10002,10005],{},"从环境变量读（需 feature ",[53,10003,10004],{},"env","）",[1590,10007,10008],{},"密钥不入命令行历史",[1572,10010,10011,10016,10019],{},[1590,10012,10013],{},[53,10014,10015],{},"value_parser = ...",[1590,10017,10018],{},"自定义校验\u002F转换",[1590,10020,10021],{},[53,10022,10023],{},"value_parser!(u16).range(1..)",[1572,10025,10026,10031,10034],{},[1590,10027,10028],{},[53,10029,10030],{},"required = true",[1590,10032,10033],{},"必填（Option 字段天然可选，无需此项）",[1590,10035,10036],{},"位置参数默认必填",[1572,10038,10039,10047,10050],{},[1590,10040,10041,2688,10044],{},[53,10042,10043],{},"conflicts_with",[53,10045,10046],{},"requires",[1590,10048,10049],{},"参数间约束",[1590,10051,10052,10055,10056,10059],{},[53,10053,10054],{},"--json"," 与 ",[53,10057,10058],{},"--yaml"," 互斥",[1572,10061,10062,10067,10070],{},[1590,10063,10064],{},[53,10065,10066],{},"hide = true",[1590,10068,10069],{},"不在 help 中显示",[1590,10071,10072],{},"内部调试参数",[24,10074],{},[27,10076,10078],{"id":10077},"第-7-章-log-env_logger-tracing日志","第 7 章 log \u002F env_logger \u002F tracing：日志",[58,10080,10082],{"id":10081},"_71-它解决什么问题","7.1 它解决什么问题",[15,10084,10085,10088,10089,10095,10096,10099],{},[53,10086,10087],{},"println!"," 无法控制级别、无法关闭、无法加时间戳和模块名。Rust 日志生态分两层：",[19,10090,10091,10094],{},[53,10092,10093],{},"log"," 是门面（facade）","——库代码只调 log 的宏，不关心日志去哪；",[19,10097,10098],{},"env_logger \u002F tracing-subscriber 是实现（backend）","——由最终的应用决定日志格式和去向。",[15,10101,10102,10105],{},[19,10103,10104],{},"为什么库必须只用 log 门面？"," 如果每个库自己初始化输出，日志格式就乱了，而且库不应该替应用做决定。这个「门面 + 实现」分层让任何库的日志都能被应用统一接管。",[58,10107,10109],{"id":10108},"_72-log-env_logger简单够用的方案","7.2 log + env_logger：简单够用的方案",[63,10111,10113],{"className":144,"code":10112,"language":146,"meta":68,"style":68},"[dependencies]\nlog = \"0.4\"\nenv_logger = \"0.11\"\n",[53,10114,10115,10119,10124],{"__ignoreMap":68},[72,10116,10117],{"class":74,"line":75},[72,10118,153],{},[72,10120,10121],{"class":74,"line":86},[72,10122,10123],{},"log = \"0.4\"\n",[72,10125,10126],{"class":74,"line":107},[72,10127,10128],{},"env_logger = \"0.11\"\n",[63,10130,10132],{"className":428,"code":10131,"language":430,"meta":68,"style":68},"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",[53,10133,10134,10170,10174,10184,10191,10198,10210,10214,10231,10243,10260,10277,10294],{"__ignoreMap":68},[72,10135,10136,10138,10141,10143,10145,10148,10150,10153,10155,10158,10160,10163,10165,10168],{"class":74,"line":75},[72,10137,437],{"class":100},[72,10139,10140],{"class":440}," log",[72,10142,443],{"class":100},[72,10144,447],{"class":446},[72,10146,10147],{"class":473},"trace",[72,10149,453],{"class":446},[72,10151,10152],{"class":473}," debug",[72,10154,453],{"class":446},[72,10156,10157],{"class":473}," info",[72,10159,453],{"class":446},[72,10161,10162],{"class":473}," warn",[72,10164,453],{"class":446},[72,10166,10167],{"class":473}," error",[72,10169,459],{"class":446},[72,10171,10172],{"class":74,"line":86},[72,10173,465],{"emptyLinePlaceholder":464},[72,10175,10176,10178,10180,10182],{"class":74,"line":107},[72,10177,569],{"class":100},[72,10179,572],{"class":89},[72,10181,575],{"class":446},[72,10183,503],{"class":446},[72,10185,10186,10188],{"class":74,"line":122},[72,10187,596],{"class":78},[72,10189,10190],{"class":82}," 只在应用的 main 里初始化一次；库代码里绝不要调用 init\n",[72,10192,10193,10195],{"class":74,"line":260},[72,10194,596],{"class":78},[72,10196,10197],{"class":82}," 默认级别由环境变量 RUST_LOG 控制，无需改代码\n",[72,10199,10200,10203,10205,10208],{"class":74,"line":274},[72,10201,10202],{"class":440},"    env_logger",[72,10204,443],{"class":100},[72,10206,10207],{"class":89},"init",[72,10209,1500],{"class":446},[72,10211,10212],{"class":74,"line":287},[72,10213,465],{"emptyLinePlaceholder":464},[72,10215,10216,10219,10221,10224,10226,10228],{"class":74,"line":301},[72,10217,10218],{"class":89},"    error!",[72,10220,476],{"class":446},[72,10222,10223],{"class":93},"\"出错了\"",[72,10225,712],{"class":446},[72,10227,1887],{"class":78},[72,10229,10230],{"class":82}," 默认只显示 warn 及以上\n",[72,10232,10233,10236,10238,10241],{"class":74,"line":317},[72,10234,10235],{"class":89},"    warn!",[72,10237,476],{"class":446},[72,10239,10240],{"class":93},"\"警告\"",[72,10242,766],{"class":446},[72,10244,10245,10248,10250,10253,10255,10257],{"class":74,"line":331},[72,10246,10247],{"class":89},"    info!",[72,10249,476],{"class":446},[72,10251,10252],{"class":93},"\"普通信息\"",[72,10254,712],{"class":446},[72,10256,3837],{"class":78},[72,10258,10259],{"class":82}," RUST_LOG=info 才显示\n",[72,10261,10262,10265,10267,10270,10272,10274],{"class":74,"line":353},[72,10263,10264],{"class":89},"    debug!",[72,10266,476],{"class":446},[72,10268,10269],{"class":93},"\"调试细节\"",[72,10271,712],{"class":446},[72,10273,552],{"class":78},[72,10275,10276],{"class":82}," RUST_LOG=debug\n",[72,10278,10279,10282,10284,10287,10289,10291],{"class":74,"line":602},[72,10280,10281],{"class":89},"    trace!",[72,10283,476],{"class":446},[72,10285,10286],{"class":93},"\"最啰嗦\"",[72,10288,712],{"class":446},[72,10290,1887],{"class":78},[72,10292,10293],{"class":82}," RUST_LOG=trace\n",[72,10295,10296],{"class":74,"line":657},[72,10297,560],{"class":446},[63,10299,10301],{"className":65,"code":10300,"language":67,"meta":68,"style":68},"RUST_LOG=info cargo run                  # 全局 info 级\nRUST_LOG=myapp=debug,reqwest=warn cargo run  # 按模块精细控制：自己代码 debug，第三方库只留 warn\n",[53,10302,10303,10323],{"__ignoreMap":68},[72,10304,10305,10308,10310,10313,10315,10317,10320],{"class":74,"line":75},[72,10306,10307],{"class":508},"RUST_LOG",[72,10309,910],{"class":100},[72,10311,10312],{"class":93},"info",[72,10314,341],{"class":89},[72,10316,265],{"class":93},[72,10318,10319],{"class":78},"                  #",[72,10321,10322],{"class":82}," 全局 info 级\n",[72,10324,10325,10327,10329,10332,10334,10337,10339,10341,10344,10346,10348,10350],{"class":74,"line":86},[72,10326,10307],{"class":508},[72,10328,910],{"class":100},[72,10330,10331],{"class":508},"myapp",[72,10333,910],{"class":100},[72,10335,10336],{"class":93},"debug,",[72,10338,5745],{"class":508},[72,10340,910],{"class":100},[72,10342,10343],{"class":93},"warn",[72,10345,341],{"class":89},[72,10347,265],{"class":93},[72,10349,226],{"class":78},[72,10351,10352],{"class":82}," 按模块精细控制：自己代码 debug，第三方库只留 warn\n",[15,10354,10355,10358,10359,10362],{},[19,10356,10357],{},"为什么要按模块控制级别？"," 开全局 debug 时，tokio\u002Fhyper 等底层库的海量日志会淹没你自己的日志。",[53,10360,10361],{},"myapp=debug,reqwest=warn"," 这种过滤让你只看想看的。",[58,10364,10366],{"id":10365},"_73-tracing结构化日志-调用链服务端推荐","7.3 tracing：结构化日志 + 调用链（服务端推荐）",[15,10368,10369,10370,10373,10374,10376],{},"log 记录的是一条条孤立的字符串；tracing 记录的是",[19,10371,10372],{},"带结构化字段的事件","，外加 ",[53,10375,72],{}," 表达「这堆日志属于哪个请求」——异步服务端排查问题的神器。",[63,10378,10380],{"className":144,"code":10379,"language":146,"meta":68,"style":68},"[dependencies]\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\", \"json\"] }\n",[53,10381,10382,10386,10391],{"__ignoreMap":68},[72,10383,10384],{"class":74,"line":75},[72,10385,153],{},[72,10387,10388],{"class":74,"line":86},[72,10389,10390],{},"tracing = \"0.1\"\n",[72,10392,10393],{"class":74,"line":107},[72,10394,10395],{},"tracing-subscriber = { version = \"0.3\", features = [\"env-filter\", \"json\"] }\n",[63,10397,10399],{"className":428,"code":10398,"language":430,"meta":68,"style":68},"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",[53,10400,10401,10430,10434,10441,10448,10486,10522,10533,10544,10570,10574,10578,10586,10599,10615,10619,10623,10632,10658,10662,10674,10686,10698,10712,10729,10754,10761,10769,10773,10785,10807],{"__ignoreMap":68},[72,10402,10403,10405,10408,10410,10412,10414,10416,10418,10420,10423,10425,10428],{"class":74,"line":75},[72,10404,437],{"class":100},[72,10406,10407],{"class":440}," tracing",[72,10409,443],{"class":100},[72,10411,447],{"class":446},[72,10413,10312],{"class":473},[72,10415,453],{"class":446},[72,10417,10162],{"class":473},[72,10419,453],{"class":446},[72,10421,10422],{"class":473}," instrument",[72,10424,453],{"class":446},[72,10426,10427],{"class":473}," info_span",[72,10429,459],{"class":446},[72,10431,10432],{"class":74,"line":86},[72,10433,465],{"emptyLinePlaceholder":464},[72,10435,10436,10438],{"class":74,"line":107},[72,10437,999],{"class":78},[72,10439,10440],{"class":82}," #[instrument] 自动给函数建 span，参数自动记录为字段\n",[72,10442,10443,10445],{"class":74,"line":122},[72,10444,999],{"class":78},[72,10446,10447],{"class":82}," 这个函数内的所有日志都会自动带上 request_id 和 user 字段——无需手动传递！\n",[72,10449,10450,10452,10455,10457,10460,10462,10465,10467,10470,10472,10475,10477,10480,10483],{"class":74,"line":260},[72,10451,470],{"class":446},[72,10453,10454],{"class":473},"instrument",[72,10456,476],{"class":446},[72,10458,10459],{"class":473},"skip",[72,10461,476],{"class":446},[72,10463,10464],{"class":473},"db",[72,10466,2399],{"class":446},[72,10468,10469],{"class":473}," fields",[72,10471,476],{"class":446},[72,10473,10474],{"class":473},"request_id ",[72,10476,910],{"class":100},[72,10478,10479],{"class":100}," %",[72,10481,10482],{"class":473},"uuid_v4",[72,10484,10485],{"class":446},"()))]\n",[72,10487,10488,10490,10492,10495,10497,10500,10502,10504,10506,10508,10511,10513,10515,10518,10520],{"class":74,"line":274},[72,10489,2791],{"class":100},[72,10491,2794],{"class":100},[72,10493,10494],{"class":89}," handle_request",[72,10496,476],{"class":446},[72,10498,10499],{"class":508},"user",[72,10501,512],{"class":100},[72,10503,1715],{"class":100},[72,10505,1718],{"class":440},[72,10507,453],{"class":446},[72,10509,10510],{"class":508}," db",[72,10512,512],{"class":100},[72,10514,1715],{"class":100},[72,10516,10517],{"class":440},"Database",[72,10519,682],{"class":446},[72,10521,503],{"class":446},[72,10523,10524,10526,10528,10531],{"class":74,"line":287},[72,10525,10247],{"class":89},[72,10527,476],{"class":446},[72,10529,10530],{"class":93},"\"开始处理\"",[72,10532,766],{"class":446},[72,10534,10535,10538,10540,10542],{"class":74,"line":301},[72,10536,10537],{"class":89},"    step1",[72,10539,575],{"class":446},[72,10541,2879],{"class":100},[72,10543,688],{"class":446},[72,10545,10546,10548,10550,10553,10555,10558,10560,10563,10565,10567],{"class":74,"line":317},[72,10547,10235],{"class":89},[72,10549,476],{"class":446},[72,10551,10552],{"class":508},"latency_ms",[72,10554,611],{"class":100},[72,10556,10557],{"class":89}," 120",[72,10559,453],{"class":446},[72,10561,10562],{"class":93}," \"某步骤偏慢\"",[72,10564,712],{"class":446},[72,10566,552],{"class":78},[72,10568,10569],{"class":82}," 结构化字段：可以被日志系统检索、聚合\n",[72,10571,10572],{"class":74,"line":331},[72,10573,560],{"class":446},[72,10575,10576],{"class":74,"line":353},[72,10577,465],{"emptyLinePlaceholder":464},[72,10579,10580,10582,10584],{"class":74,"line":602},[72,10581,470],{"class":446},[72,10583,10454],{"class":473},[72,10585,2786],{"class":446},[72,10587,10588,10590,10592,10595,10597],{"class":74,"line":657},[72,10589,2791],{"class":100},[72,10591,2794],{"class":100},[72,10593,10594],{"class":89}," step1",[72,10596,575],{"class":446},[72,10598,503],{"class":446},[72,10600,10601,10603,10605,10608,10610,10612],{"class":74,"line":691},[72,10602,10247],{"class":89},[72,10604,476],{"class":446},[72,10606,10607],{"class":93},"\"步骤1\"",[72,10609,712],{"class":446},[72,10611,552],{"class":78},[72,10613,10614],{"class":82}," 自动带上父 span 的 request_id、user\n",[72,10616,10617],{"class":74,"line":720},[72,10618,560],{"class":446},[72,10620,10621],{"class":74,"line":725},[72,10622,465],{"emptyLinePlaceholder":464},[72,10624,10625,10627,10630],{"class":74,"line":733},[72,10626,497],{"class":100},[72,10628,10629],{"class":440}," Database",[72,10631,688],{"class":446},[72,10633,10634,10636,10639,10641,10643,10645,10647,10650,10652,10654,10656],{"class":74,"line":769},[72,10635,569],{"class":100},[72,10637,10638],{"class":89}," uuid_v4",[72,10640,575],{"class":446},[72,10642,578],{"class":100},[72,10644,515],{"class":440},[72,10646,616],{"class":446},[72,10648,10649],{"class":93}," \"req-abc123\"",[72,10651,627],{"class":100},[72,10653,630],{"class":89},[72,10655,575],{"class":446},[72,10657,1181],{"class":446},[72,10659,10660],{"class":74,"line":774},[72,10661,465],{"emptyLinePlaceholder":464},[72,10663,10664,10666,10668,10670,10672],{"class":74,"line":782},[72,10665,470],{"class":446},[72,10667,2778],{"class":473},[72,10669,443],{"class":100},[72,10671,2783],{"class":473},[72,10673,2786],{"class":446},[72,10675,10676,10678,10680,10682,10684],{"class":74,"line":797},[72,10677,2791],{"class":100},[72,10679,2794],{"class":100},[72,10681,572],{"class":89},[72,10683,575],{"class":446},[72,10685,503],{"class":446},[72,10687,10688,10691,10693,10696],{"class":74,"line":829},[72,10689,10690],{"class":440},"    tracing_subscriber",[72,10692,443],{"class":100},[72,10694,10695],{"class":89},"fmt",[72,10697,3984],{"class":446},[72,10699,10700,10702,10705,10707,10709],{"class":74,"line":849},[72,10701,1776],{"class":100},[72,10703,10704],{"class":89},"with_env_filter",[72,10706,476],{"class":446},[72,10708,552],{"class":78},[72,10710,10711],{"class":82}," 同样支持 RUST_LOG 环境变量过滤\n",[72,10713,10714,10717,10719,10722,10724,10727],{"class":74,"line":858},[72,10715,10716],{"class":440},"            tracing_subscriber",[72,10718,443],{"class":100},[72,10720,10721],{"class":440},"EnvFilter",[72,10723,443],{"class":100},[72,10725,10726],{"class":89},"try_from_default_env",[72,10728,3984],{"class":446},[72,10730,10731,10734,10737,10739,10741,10743,10745,10748,10750,10752],{"class":74,"line":2023},[72,10732,10733],{"class":100},"                .",[72,10735,10736],{"class":89},"unwrap_or_else",[72,10738,476],{"class":446},[72,10740,7401],{"class":100},[72,10742,3585],{"class":508},[72,10744,7401],{"class":100},[72,10746,10747],{"class":93}," \"myapp=info\"",[72,10749,627],{"class":100},[72,10751,630],{"class":89},[72,10753,8437],{"class":446},[72,10755,10756,10758],{"class":74,"line":2028},[72,10757,2826],{"class":78},[72,10759,10760],{"class":82}," .json()      \u002F\u002F 生产环境开 JSON 输出，对接 ELK\u002FLoki 等日志系统\n",[72,10762,10763,10765,10767],{"class":74,"line":2036},[72,10764,1776],{"class":100},[72,10766,10207],{"class":89},[72,10768,1500],{"class":446},[72,10770,10771],{"class":74,"line":2069},[72,10772,465],{"emptyLinePlaceholder":464},[72,10774,10775,10777,10779,10781,10783],{"class":74,"line":2096},[72,10776,605],{"class":100},[72,10778,10510],{"class":508},[72,10780,611],{"class":100},[72,10782,10629],{"class":440},[72,10784,688],{"class":446},[72,10786,10787,10790,10792,10795,10797,10799,10801,10803,10805],{"class":74,"line":2132},[72,10788,10789],{"class":89},"    handle_request",[72,10791,476],{"class":446},[72,10793,10794],{"class":93},"\"alice\"",[72,10796,453],{"class":446},[72,10798,1715],{"class":100},[72,10800,10464],{"class":508},[72,10802,682],{"class":446},[72,10804,2879],{"class":100},[72,10806,688],{"class":446},[72,10808,10809],{"class":74,"line":2137},[72,10810,560],{"class":446},[15,10812,10813],{},[19,10814,10815],{},"输出（文本模式）：",[63,10817,10821],{"className":10818,"code":10820,"language":5031,"meta":68},[10819],"language-text","INFO handle_request{request_id=\"req-abc123\" user=\"alice\"}: 开始处理\nINFO handle_request{request_id=\"req-abc123\" user=\"alice\"}:step1: 步骤1\n",[53,10822,10820],{"__ignoreMap":68},[15,10824,10825,10828,10829,10831,10832,10835],{},[19,10826,10827],{},"为什么线上要 JSON 输出？"," 文本日志只能靠 grep；JSON 日志里 ",[53,10830,10552],{},"、",[53,10833,10834],{},"request_id"," 是可索引字段，可以在日志平台做「查 request_id=xxx 的全部日志」「统计 latency_ms 的 P99」这类查询。",[12,10837,10838],{},[15,10839,10840,10841,10850,10851,10854],{},"建议：",[19,10842,10843,10844,10846,10847],{},"写库用 ",[53,10845,10093],{},"，写服务端应用用 ",[53,10848,10849],{},"tracing","。tracing 兼容 log 的宏（加 ",[53,10852,10853],{},"tracing-log"," 桥），迁移成本低。",[24,10856],{},[27,10858,10860],{"id":10859},"第-8-章-chrono日期与时间","第 8 章 chrono：日期与时间",[58,10862,10864],{"id":10863},"_81-它解决什么问题","8.1 它解决什么问题",[15,10866,10867,10870],{},[53,10868,10869],{},"std::time::SystemTime"," 只给时间戳，不管时区、格式化、日期运算。chrono 是生态标准的日期时间库。",[63,10872,10874],{"className":144,"code":10873,"language":146,"meta":68,"style":68},"[dependencies]\nchrono = { version = \"0.4\", features = [\"serde\"] } # serde feature 让时间字段可直接序列化\n",[53,10875,10876,10880],{"__ignoreMap":68},[72,10877,10878],{"class":74,"line":75},[72,10879,153],{},[72,10881,10882],{"class":74,"line":86},[72,10883,10884],{},"chrono = { version = \"0.4\", features = [\"serde\"] } # serde feature 让时间字段可直接序列化\n",[58,10886,10888],{"id":10887},"_82-常用操作","8.2 常用操作",[63,10890,10892],{"className":428,"code":10891,"language":430,"meta":68,"style":68},"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",[53,10893,10894,10944,10948,10958,10965,10987,11010,11030,11050,11054,11061,11093,11122,11151,11182,11186,11193,11231,11245,11295,11299,11306,11338,11386,11413,11438,11460,11464,11471,11540],{"__ignoreMap":68},[72,10895,10896,10898,10901,10903,10905,10908,10910,10913,10915,10918,10920,10923,10925,10927,10929,10932,10934,10937,10939,10942],{"class":74,"line":75},[72,10897,437],{"class":100},[72,10899,10900],{"class":440}," chrono",[72,10902,443],{"class":100},[72,10904,447],{"class":446},[72,10906,10907],{"class":440},"Local",[72,10909,453],{"class":446},[72,10911,10912],{"class":440}," Utc",[72,10914,453],{"class":446},[72,10916,10917],{"class":440}," NaiveDate",[72,10919,453],{"class":446},[72,10921,10922],{"class":440}," DateTime",[72,10924,453],{"class":446},[72,10926,2973],{"class":440},[72,10928,453],{"class":446},[72,10930,10931],{"class":440}," TimeZone",[72,10933,453],{"class":446},[72,10935,10936],{"class":440}," Datelike",[72,10938,453],{"class":446},[72,10940,10941],{"class":440}," Timelike",[72,10943,459],{"class":446},[72,10945,10946],{"class":74,"line":86},[72,10947,465],{"emptyLinePlaceholder":464},[72,10949,10950,10952,10954,10956],{"class":74,"line":107},[72,10951,569],{"class":100},[72,10953,572],{"class":89},[72,10955,575],{"class":446},[72,10957,503],{"class":446},[72,10959,10960,10962],{"class":74,"line":122},[72,10961,596],{"class":78},[72,10963,10964],{"class":82}," 1) 获取当前时间\n",[72,10966,10967,10969,10972,10974,10976,10978,10980,10982,10984],{"class":74,"line":260},[72,10968,605],{"class":100},[72,10970,10971],{"class":508}," now_utc",[72,10973,611],{"class":100},[72,10975,10912],{"class":440},[72,10977,443],{"class":100},[72,10979,8527],{"class":89},[72,10981,4306],{"class":446},[72,10983,6469],{"class":78},[72,10985,10986],{"class":82}," DateTime\u003CUtc>：服务器存时间永远用 UTC\n",[72,10988,10989,10991,10994,10996,10999,11001,11003,11005,11007],{"class":74,"line":274},[72,10990,605],{"class":100},[72,10992,10993],{"class":508}," now_local",[72,10995,611],{"class":100},[72,10997,10998],{"class":440}," Local",[72,11000,443],{"class":100},[72,11002,8527],{"class":89},[72,11004,4306],{"class":446},[72,11006,1887],{"class":78},[72,11008,11009],{"class":82}," DateTime\u003CLocal>：只用于展示给用户\n",[72,11011,11012,11014,11016,11019,11021,11024,11026,11028],{"class":74,"line":287},[72,11013,694],{"class":89},[72,11015,476],{"class":446},[72,11017,11018],{"class":93},"\"UTC:   ",[72,11020,447],{"class":446},[72,11022,11023],{"class":93},"now_utc",[72,11025,707],{"class":446},[72,11027,699],{"class":93},[72,11029,766],{"class":446},[72,11031,11032,11034,11036,11039,11041,11044,11046,11048],{"class":74,"line":301},[72,11033,694],{"class":89},[72,11035,476],{"class":446},[72,11037,11038],{"class":93},"\"本地:  ",[72,11040,447],{"class":446},[72,11042,11043],{"class":93},"now_local",[72,11045,707],{"class":446},[72,11047,699],{"class":93},[72,11049,766],{"class":446},[72,11051,11052],{"class":74,"line":317},[72,11053,465],{"emptyLinePlaceholder":464},[72,11055,11056,11058],{"class":74,"line":331},[72,11057,596],{"class":78},[72,11059,11060],{"class":82}," 2) 格式化与解析\n",[72,11062,11063,11065,11067,11069,11071,11073,11075,11077,11079,11081,11083,11086,11088,11090],{"class":74,"line":353},[72,11064,694],{"class":89},[72,11066,476],{"class":446},[72,11068,699],{"class":93},[72,11070,742],{"class":446},[72,11072,699],{"class":93},[72,11074,453],{"class":446},[72,11076,10971],{"class":508},[72,11078,627],{"class":100},[72,11080,9811],{"class":89},[72,11082,476],{"class":446},[72,11084,11085],{"class":93},"\"%Y-%m-%d %H:%M:%S\"",[72,11087,2483],{"class":446},[72,11089,1033],{"class":78},[72,11091,11092],{"class":82}," 自定义格式\n",[72,11094,11095,11097,11099,11101,11103,11105,11107,11109,11111,11114,11116,11119],{"class":74,"line":602},[72,11096,694],{"class":89},[72,11098,476],{"class":446},[72,11100,699],{"class":93},[72,11102,742],{"class":446},[72,11104,699],{"class":93},[72,11106,453],{"class":446},[72,11108,10971],{"class":508},[72,11110,627],{"class":100},[72,11112,11113],{"class":89},"to_rfc3339",[72,11115,2124],{"class":446},[72,11117,11118],{"class":78},"                          \u002F\u002F",[72,11120,11121],{"class":82}," 2026-09-28T12:00:00+00:00，API 交换标准格式\n",[72,11123,11124,11126,11129,11131,11133,11135,11138,11140,11143,11145,11147,11149],{"class":74,"line":657},[72,11125,605],{"class":100},[72,11127,11128],{"class":508}," dt",[72,11130,611],{"class":100},[72,11132,10922],{"class":440},[72,11134,443],{"class":100},[72,11136,11137],{"class":89},"parse_from_rfc3339",[72,11139,476],{"class":446},[72,11141,11142],{"class":93},"\"2026-09-28T12:00:00+08:00\"",[72,11144,682],{"class":446},[72,11146,627],{"class":100},[72,11148,1497],{"class":89},[72,11150,1500],{"class":446},[72,11152,11153,11155,11158,11160,11162,11164,11167,11169,11171,11174,11176,11179],{"class":74,"line":691},[72,11154,605],{"class":100},[72,11156,11157],{"class":508}," utc_dt",[72,11159,611],{"class":100},[72,11161,11128],{"class":508},[72,11163,627],{"class":100},[72,11165,11166],{"class":89},"with_timezone",[72,11168,476],{"class":446},[72,11170,676],{"class":100},[72,11172,11173],{"class":440},"Utc",[72,11175,712],{"class":446},[72,11177,11178],{"class":78},"                           \u002F\u002F",[72,11180,11181],{"class":82}," 时区转换是零成本的（内部都是时间戳）\n",[72,11183,11184],{"class":74,"line":720},[72,11185,465],{"emptyLinePlaceholder":464},[72,11187,11188,11190],{"class":74,"line":725},[72,11189,596],{"class":78},[72,11191,11192],{"class":82}," 3) 从日期构造时间\n",[72,11194,11195,11197,11200,11202,11204,11206,11209,11211,11214,11216,11219,11221,11224,11226,11228],{"class":74,"line":733},[72,11196,605],{"class":100},[72,11198,11199],{"class":508}," d",[72,11201,611],{"class":100},[72,11203,10917],{"class":440},[72,11205,443],{"class":100},[72,11207,11208],{"class":89},"from_ymd_opt",[72,11210,476],{"class":446},[72,11212,11213],{"class":89},"2026",[72,11215,453],{"class":446},[72,11217,11218],{"class":89}," 2",[72,11220,453],{"class":446},[72,11222,11223],{"class":89}," 29",[72,11225,682],{"class":446},[72,11227,1887],{"class":78},[72,11229,11230],{"class":82}," Opt 版本返回 Option，不 panic\n",[72,11232,11233,11235,11238,11240,11243],{"class":74,"line":769},[72,11234,1776],{"class":100},[72,11236,11237],{"class":89},"expect",[72,11239,476],{"class":446},[72,11241,11242],{"class":93},"\"2026 不是闰年，这里会 panic\"",[72,11244,766],{"class":446},[72,11246,11247,11249,11252,11254,11256,11258,11261,11263,11265,11268,11270,11273,11275,11278,11280,11283,11285,11287,11289,11291,11293],{"class":74,"line":774},[72,11248,605],{"class":100},[72,11250,11251],{"class":508}," dt2",[72,11253,611],{"class":100},[72,11255,10912],{"class":440},[72,11257,627],{"class":100},[72,11259,11260],{"class":89},"from_utc_datetime",[72,11262,476],{"class":446},[72,11264,676],{"class":100},[72,11266,11267],{"class":508},"d",[72,11269,627],{"class":100},[72,11271,11272],{"class":89},"and_hms_opt",[72,11274,476],{"class":446},[72,11276,11277],{"class":89},"8",[72,11279,453],{"class":446},[72,11281,11282],{"class":89}," 30",[72,11284,453],{"class":446},[72,11286,1869],{"class":89},[72,11288,682],{"class":446},[72,11290,627],{"class":100},[72,11292,1497],{"class":89},[72,11294,2020],{"class":446},[72,11296,11297],{"class":74,"line":782},[72,11298,465],{"emptyLinePlaceholder":464},[72,11300,11301,11303],{"class":74,"line":797},[72,11302,596],{"class":78},[72,11304,11305],{"class":82}," 4) 时间运算\n",[72,11307,11308,11310,11313,11315,11317,11319,11321,11323,11325,11327,11329,11332,11334,11336],{"class":74,"line":829},[72,11309,605],{"class":100},[72,11311,11312],{"class":508}," tomorrow",[72,11314,611],{"class":100},[72,11316,10912],{"class":440},[72,11318,443],{"class":100},[72,11320,8527],{"class":89},[72,11322,575],{"class":446},[72,11324,7363],{"class":100},[72,11326,2973],{"class":440},[72,11328,443],{"class":100},[72,11330,11331],{"class":89},"days",[72,11333,476],{"class":446},[72,11335,2062],{"class":89},[72,11337,766],{"class":446},[72,11339,11340,11342,11345,11347,11349,11351,11353,11355,11357,11359,11361,11364,11366,11368,11370,11372,11374,11376,11379,11381,11384],{"class":74,"line":849},[72,11341,605],{"class":100},[72,11343,11344],{"class":508}," deadline",[72,11346,611],{"class":100},[72,11348,10912],{"class":440},[72,11350,443],{"class":100},[72,11352,8527],{"class":89},[72,11354,575],{"class":446},[72,11356,7363],{"class":100},[72,11358,2973],{"class":440},[72,11360,443],{"class":100},[72,11362,11363],{"class":89},"hours",[72,11365,476],{"class":446},[72,11367,4433],{"class":89},[72,11369,682],{"class":446},[72,11371,7363],{"class":100},[72,11373,2973],{"class":440},[72,11375,443],{"class":100},[72,11377,11378],{"class":89},"minutes",[72,11380,476],{"class":446},[72,11382,11383],{"class":89},"30",[72,11385,766],{"class":446},[72,11387,11388,11390,11393,11395,11397,11400,11402,11404,11406,11408,11410],{"class":74,"line":858},[72,11389,605],{"class":100},[72,11391,11392],{"class":508}," diff",[72,11394,611],{"class":100},[72,11396,11344],{"class":508},[72,11398,11399],{"class":100}," -",[72,11401,10912],{"class":440},[72,11403,443],{"class":100},[72,11405,8527],{"class":89},[72,11407,4306],{"class":446},[72,11409,6469],{"class":78},[72,11411,11412],{"class":82}," 减法得 Duration\n",[72,11414,11415,11417,11419,11422,11424,11427,11429,11431,11433,11436],{"class":74,"line":2023},[72,11416,694],{"class":89},[72,11418,476],{"class":446},[72,11420,11421],{"class":93},"\"还剩 ",[72,11423,742],{"class":446},[72,11425,11426],{"class":93}," 秒\"",[72,11428,453],{"class":446},[72,11430,11392],{"class":508},[72,11432,627],{"class":100},[72,11434,11435],{"class":89},"num_seconds",[72,11437,2020],{"class":446},[72,11439,11440,11442,11444,11446,11448,11450,11452,11454,11456,11458],{"class":74,"line":2028},[72,11441,605],{"class":100},[72,11443,3351],{"class":508},[72,11445,611],{"class":100},[72,11447,3163],{"class":446},[72,11449,11267],{"class":508},[72,11451,453],{"class":446},[72,11453,11251],{"class":508},[72,11455,453],{"class":446},[72,11457,11312],{"class":508},[72,11459,766],{"class":446},[72,11461,11462],{"class":74,"line":2036},[72,11463,465],{"emptyLinePlaceholder":464},[72,11465,11466,11468],{"class":74,"line":2069},[72,11467,596],{"class":78},[72,11469,11470],{"class":82}," 5) 取字段\n",[72,11472,11473,11475,11477,11479,11481,11484,11486,11489,11491,11494,11496,11498,11500,11502,11504,11506,11508,11511,11513,11515,11517,11520,11522,11524,11526,11529,11531,11533,11535,11538],{"class":74,"line":2096},[72,11474,694],{"class":89},[72,11476,476],{"class":446},[72,11478,699],{"class":93},[72,11480,742],{"class":446},[72,11482,11483],{"class":93},"年",[72,11485,742],{"class":446},[72,11487,11488],{"class":93},"月",[72,11490,742],{"class":446},[72,11492,11493],{"class":93},"日 星期",[72,11495,447],{"class":446},[72,11497,2108],{"class":93},[72,11499,707],{"class":446},[72,11501,699],{"class":93},[72,11503,453],{"class":446},[72,11505,10993],{"class":508},[72,11507,627],{"class":100},[72,11509,11510],{"class":89},"year",[72,11512,633],{"class":446},[72,11514,10993],{"class":508},[72,11516,627],{"class":100},[72,11518,11519],{"class":89},"month",[72,11521,633],{"class":446},[72,11523,10993],{"class":508},[72,11525,627],{"class":100},[72,11527,11528],{"class":89},"day",[72,11530,633],{"class":446},[72,11532,10993],{"class":508},[72,11534,627],{"class":100},[72,11536,11537],{"class":89},"weekday",[72,11539,2020],{"class":446},[72,11541,11542],{"class":74,"line":2132},[72,11543,560],{"class":446},[15,11545,11546,11549],{},[19,11547,11548],{},"为什么「存 UTC、展示转本地」是铁律？"," 夏令时、用户跨时区、服务器迁移时区——只要存了本地时间，这些场景全是坑。UTC 是无歧义的单点时间，本地时间只是「某时区下的一种显示格式」。",[58,11551,11553],{"id":11552},"_83-与-serde-配合","8.3 与 serde 配合",[63,11555,11557],{"className":428,"code":11556,"language":430,"meta":68,"style":68},"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",[53,11558,11559,11582,11600,11604,11620,11629,11639,11659,11682,11693,11697,11701,11711,11747,11771,11775,11794,11859,11892,11896,11955,11980,12027,12031],{"__ignoreMap":68},[72,11560,11561,11563,11565,11567,11569,11572,11574,11576,11578,11580],{"class":74,"line":75},[72,11562,437],{"class":100},[72,11564,10900],{"class":440},[72,11566,443],{"class":100},[72,11568,447],{"class":446},[72,11570,11571],{"class":440},"DateTime",[72,11573,453],{"class":446},[72,11575,10912],{"class":440},[72,11577,453],{"class":446},[72,11579,10917],{"class":440},[72,11581,459],{"class":446},[72,11583,11584,11586,11588,11590,11592,11594,11596,11598],{"class":74,"line":86},[72,11585,437],{"class":100},[72,11587,97],{"class":440},[72,11589,443],{"class":100},[72,11591,447],{"class":446},[72,11593,479],{"class":440},[72,11595,453],{"class":446},[72,11597,484],{"class":440},[72,11599,459],{"class":446},[72,11601,11602],{"class":74,"line":107},[72,11603,465],{"emptyLinePlaceholder":464},[72,11605,11606,11608,11610,11612,11614,11616,11618],{"class":74,"line":122},[72,11607,470],{"class":446},[72,11609,177],{"class":473},[72,11611,476],{"class":446},[72,11613,479],{"class":440},[72,11615,453],{"class":446},[72,11617,484],{"class":440},[72,11619,492],{"class":446},[72,11621,11622,11624,11627],{"class":74,"line":260},[72,11623,497],{"class":100},[72,11625,11626],{"class":440}," Event",[72,11628,503],{"class":446},[72,11630,11631,11633,11635,11637],{"class":74,"line":274},[72,11632,509],{"class":508},[72,11634,512],{"class":100},[72,11636,515],{"class":440},[72,11638,518],{"class":446},[72,11640,11641,11644,11646,11648,11650,11652,11654,11656],{"class":74,"line":287},[72,11642,11643],{"class":508},"    at",[72,11645,512],{"class":100},[72,11647,10922],{"class":440},[72,11649,543],{"class":446},[72,11651,11173],{"class":440},[72,11653,549],{"class":446},[72,11655,2358],{"class":78},[72,11657,11658],{"class":82}," 默认序列化为 RFC3339 字符串\n",[72,11660,11661,11663,11665,11667,11670,11672,11675,11677,11679],{"class":74,"line":301},[72,11662,899],{"class":446},[72,11664,902],{"class":473},[72,11666,476],{"class":446},[72,11668,11669],{"class":473},"with ",[72,11671,910],{"class":100},[72,11673,11674],{"class":93}," \"date_fmt\"",[72,11676,916],{"class":446},[72,11678,1928],{"class":78},[72,11680,11681],{"class":82}," 自定义格式时用 with + 模块\n",[72,11683,11684,11687,11689,11691],{"class":74,"line":317},[72,11685,11686],{"class":508},"    day",[72,11688,512],{"class":100},[72,11690,10917],{"class":440},[72,11692,518],{"class":446},[72,11694,11695],{"class":74,"line":331},[72,11696,560],{"class":446},[72,11698,11699],{"class":74,"line":353},[72,11700,465],{"emptyLinePlaceholder":464},[72,11702,11703,11706,11709],{"class":74,"line":602},[72,11704,11705],{"class":100},"mod",[72,11707,11708],{"class":89}," date_fmt",[72,11710,503],{"class":446},[72,11712,11713,11716,11718,11720,11722,11725,11727,11729,11731,11734,11737,11739,11742,11744],{"class":74,"line":657},[72,11714,11715],{"class":100},"    use",[72,11717,10900],{"class":440},[72,11719,443],{"class":100},[72,11721,447],{"class":446},[72,11723,11724],{"class":440},"NaiveDate",[72,11726,453],{"class":446},[72,11728,97],{"class":440},[72,11730,443],{"class":100},[72,11732,11733],{"class":473},"ts_microseconds ",[72,11735,11736],{"class":100},"as",[72,11738,3351],{"class":473},[72,11740,11741],{"class":446},"};",[72,11743,552],{"class":78},[72,11745,11746],{"class":82}," 只是示意，下面手写\n",[72,11748,11749,11751,11753,11755,11757,11759,11761,11764,11766,11769],{"class":74,"line":691},[72,11750,11715],{"class":100},[72,11752,97],{"class":440},[72,11754,443],{"class":100},[72,11756,447],{"class":446},[72,11758,450],{"class":440},[72,11760,453],{"class":446},[72,11762,11763],{"class":440}," Deserializer",[72,11765,453],{"class":446},[72,11767,11768],{"class":440}," Serializer",[72,11770,459],{"class":446},[72,11772,11773],{"class":74,"line":720},[72,11774,465],{"emptyLinePlaceholder":464},[72,11776,11777,11780,11783,11785,11787,11789,11792],{"class":74,"line":725},[72,11778,11779],{"class":100},"    const",[72,11781,11782],{"class":100}," FMT:",[72,11784,1715],{"class":100},[72,11786,1718],{"class":440},[72,11788,611],{"class":100},[72,11790,11791],{"class":93}," \"%Y\u002F%m\u002F%d\"",[72,11793,688],{"class":446},[72,11795,11796,11799,11801,11804,11806,11809,11811,11813,11815,11817,11819,11821,11823,11825,11827,11829,11832,11834,11836,11838,11840,11842,11844,11847,11849,11851,11853,11855,11857],{"class":74,"line":733},[72,11797,11798],{"class":100},"    pub",[72,11800,2794],{"class":100},[72,11802,11803],{"class":89}," serialize",[72,11805,543],{"class":446},[72,11807,11808],{"class":440},"S",[72,11810,512],{"class":100},[72,11812,11768],{"class":440},[72,11814,4390],{"class":446},[72,11816,11267],{"class":508},[72,11818,512],{"class":100},[72,11820,1715],{"class":100},[72,11822,11724],{"class":440},[72,11824,453],{"class":446},[72,11826,662],{"class":508},[72,11828,512],{"class":100},[72,11830,11831],{"class":440}," S",[72,11833,682],{"class":446},[72,11835,578],{"class":100},[72,11837,1662],{"class":440},[72,11839,543],{"class":446},[72,11841,11808],{"class":440},[72,11843,443],{"class":100},[72,11845,11846],{"class":440},"Ok",[72,11848,453],{"class":446},[72,11850,11831],{"class":440},[72,11852,443],{"class":100},[72,11854,2193],{"class":440},[72,11856,1731],{"class":446},[72,11858,503],{"class":446},[72,11860,11861,11864,11866,11869,11871,11873,11875,11877,11879,11881,11884,11886,11888,11890],{"class":74,"line":769},[72,11862,11863],{"class":508},"        s",[72,11865,627],{"class":100},[72,11867,11868],{"class":89},"serialize_str",[72,11870,476],{"class":446},[72,11872,676],{"class":100},[72,11874,11267],{"class":508},[72,11876,627],{"class":100},[72,11878,9811],{"class":89},[72,11880,476],{"class":446},[72,11882,11883],{"class":100},"FMT",[72,11885,682],{"class":446},[72,11887,627],{"class":100},[72,11889,671],{"class":89},[72,11891,2501],{"class":446},[72,11893,11894],{"class":74,"line":774},[72,11895,1936],{"class":446},[72,11897,11898,11900,11902,11905,11908,11911,11913,11916,11918,11920,11922,11924,11927,11929,11931,11933,11935,11937,11939,11941,11943,11945,11947,11949,11951,11953],{"class":74,"line":782},[72,11899,11798],{"class":100},[72,11901,2794],{"class":100},[72,11903,11904],{"class":89}," deserialize",[72,11906,11907],{"class":446},"\u003C'",[72,11909,11910],{"class":440},"de",[72,11912,453],{"class":446},[72,11914,11915],{"class":440}," D",[72,11917,512],{"class":100},[72,11919,11763],{"class":440},[72,11921,11907],{"class":446},[72,11923,11910],{"class":440},[72,11925,11926],{"class":446},">>(",[72,11928,11267],{"class":508},[72,11930,512],{"class":100},[72,11932,11915],{"class":440},[72,11934,682],{"class":446},[72,11936,578],{"class":100},[72,11938,1662],{"class":440},[72,11940,543],{"class":446},[72,11942,11724],{"class":440},[72,11944,453],{"class":446},[72,11946,11915],{"class":440},[72,11948,443],{"class":100},[72,11950,2193],{"class":440},[72,11952,1731],{"class":446},[72,11954,503],{"class":446},[72,11956,11957,11959,11961,11963,11965,11967,11970,11972,11974,11976,11978],{"class":74,"line":797},[72,11958,4188],{"class":100},[72,11960,662],{"class":508},[72,11962,611],{"class":100},[72,11964,515],{"class":440},[72,11966,443],{"class":100},[72,11968,11969],{"class":89},"deserialize",[72,11971,476],{"class":446},[72,11973,11267],{"class":508},[72,11975,682],{"class":446},[72,11977,685],{"class":100},[72,11979,688],{"class":446},[72,11981,11982,11985,11987,11990,11992,11994,11996,11998,12001,12003,12005,12008,12010,12012,12014,12016,12018,12020,12022,12025],{"class":74,"line":829},[72,11983,11984],{"class":440},"        NaiveDate",[72,11986,443],{"class":100},[72,11988,11989],{"class":89},"parse_from_str",[72,11991,476],{"class":446},[72,11993,676],{"class":100},[72,11995,704],{"class":508},[72,11997,453],{"class":446},[72,11999,12000],{"class":100}," FMT",[72,12002,682],{"class":446},[72,12004,627],{"class":100},[72,12006,12007],{"class":89},"map_err",[72,12009,476],{"class":446},[72,12011,902],{"class":440},[72,12013,443],{"class":100},[72,12015,11910],{"class":440},[72,12017,443],{"class":100},[72,12019,2193],{"class":440},[72,12021,443],{"class":100},[72,12023,12024],{"class":508},"custom",[72,12026,1771],{"class":446},[72,12028,12029],{"class":74,"line":849},[72,12030,1936],{"class":446},[72,12032,12033],{"class":74,"line":858},[72,12034,560],{"class":446},[12,12036,12037],{},[15,12038,12039,12040,2688,12043,12046,12047,12050],{},"小知识：chrono 自带 ",[53,12041,12042],{},"chrono::serde::ts_seconds",[53,12044,12045],{},"ts_milliseconds"," 模块，",[53,12048,12049],{},"#[serde(with = \"chrono::serde::ts_seconds\")]"," 可以直接把时间序列化为时间戳数字，无需手写。",[24,12052],{},[27,12054,12056],{"id":12055},"第-9-章-rand随机数","第 9 章 rand：随机数",[63,12058,12060],{"className":144,"code":12059,"language":146,"meta":68,"style":68},"[dependencies]\nrand = \"0.9\"\n",[53,12061,12062,12066],{"__ignoreMap":68},[72,12063,12064],{"class":74,"line":75},[72,12065,153],{},[72,12067,12068],{"class":74,"line":86},[72,12069,12070],{},"rand = \"0.9\"\n",[12,12072,12073],{},[15,12074,12075,12076,12079,12080,2727,12083,12079,12086,2727,12089,12079,12092,376],{},"rand 0.9（2025 年起的主流版本）API 有变化：",[53,12077,12078],{},"thread_rng()"," → ",[53,12081,12082],{},"rng()",[53,12084,12085],{},"gen()",[53,12087,12088],{},"random()",[53,12090,12091],{},"gen_range()",[53,12093,12094],{},"random_range()",[63,12096,12098],{"className":428,"code":12097,"language":430,"meta":68,"style":68},"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",[53,12099,12100,12117,12121,12131,12156,12160,12167,12195,12216,12242,12246,12253,12286,12324,12328,12335,12369,12373,12380,12416,12439,12474,12524,12528,12535,12599,12624,12649,12653,12727,12731,12738,12778,12799],{"__ignoreMap":68},[72,12101,12102,12104,12107,12109,12112,12115],{"class":74,"line":75},[72,12103,437],{"class":100},[72,12105,12106],{"class":440}," rand",[72,12108,443],{"class":100},[72,12110,12111],{"class":440},"prelude",[72,12113,12114],{"class":100},"::*",[72,12116,688],{"class":446},[72,12118,12119],{"class":74,"line":86},[72,12120,465],{"emptyLinePlaceholder":464},[72,12122,12123,12125,12127,12129],{"class":74,"line":107},[72,12124,569],{"class":100},[72,12126,572],{"class":89},[72,12128,575],{"class":446},[72,12130,503],{"class":446},[72,12132,12133,12135,12137,12140,12142,12144,12146,12149,12151,12153],{"class":74,"line":122},[72,12134,605],{"class":100},[72,12136,3781],{"class":100},[72,12138,12139],{"class":508}," rng",[72,12141,611],{"class":100},[72,12143,12106],{"class":440},[72,12145,443],{"class":100},[72,12147,12148],{"class":89},"rng",[72,12150,4306],{"class":446},[72,12152,1033],{"class":78},[72,12154,12155],{"class":82}," 线程局部、密码学安全的默认随机源\n",[72,12157,12158],{"class":74,"line":260},[72,12159,465],{"emptyLinePlaceholder":464},[72,12161,12162,12164],{"class":74,"line":274},[72,12163,596],{"class":78},[72,12165,12166],{"class":82}," 1) 基本类型\n",[72,12168,12169,12171,12174,12176,12179,12181,12183,12185,12188,12190,12192],{"class":74,"line":287},[72,12170,605],{"class":100},[72,12172,12173],{"class":508}," x",[72,12175,512],{"class":100},[72,12177,12178],{"class":440}," u32",[72,12180,611],{"class":100},[72,12182,12139],{"class":508},[72,12184,627],{"class":100},[72,12186,12187],{"class":89},"random",[72,12189,4306],{"class":446},[72,12191,1033],{"class":78},[72,12193,12194],{"class":82}," 任意实现了相应 trait 的类型\n",[72,12196,12197,12199,12202,12204,12206,12208,12210,12212,12214],{"class":74,"line":301},[72,12198,605],{"class":100},[72,12200,12201],{"class":508}," b",[72,12203,512],{"class":100},[72,12205,9615],{"class":440},[72,12207,611],{"class":100},[72,12209,12139],{"class":508},[72,12211,627],{"class":100},[72,12213,12187],{"class":89},[72,12215,1500],{"class":446},[72,12217,12218,12220,12222,12224,12227,12229,12231,12233,12235,12237,12239],{"class":74,"line":317},[72,12219,605],{"class":100},[72,12221,3784],{"class":508},[72,12223,512],{"class":100},[72,12225,12226],{"class":440}," f64",[72,12228,611],{"class":100},[72,12230,12139],{"class":508},[72,12232,627],{"class":100},[72,12234,12187],{"class":89},[72,12236,4306],{"class":446},[72,12238,1033],{"class":78},[72,12240,12241],{"class":82}," [0, 1) 均匀分布\n",[72,12243,12244],{"class":74,"line":331},[72,12245,465],{"emptyLinePlaceholder":464},[72,12247,12248,12250],{"class":74,"line":353},[72,12249,596],{"class":78},[72,12251,12252],{"class":82}," 2) 范围随机\n",[72,12254,12255,12257,12260,12262,12264,12266,12269,12271,12273,12275,12278,12280,12283],{"class":74,"line":602},[72,12256,605],{"class":100},[72,12258,12259],{"class":508}," dice",[72,12261,611],{"class":100},[72,12263,12139],{"class":508},[72,12265,627],{"class":100},[72,12267,12268],{"class":89},"random_range",[72,12270,476],{"class":446},[72,12272,2062],{"class":89},[72,12274,9396],{"class":100},[72,12276,12277],{"class":89},"6",[72,12279,712],{"class":446},[72,12281,12282],{"class":78},"         \u002F\u002F",[72,12284,12285],{"class":82}," 1-6 含 6\n",[72,12287,12288,12290,12293,12295,12297,12299,12301,12303,12305,12307,12309,12311,12313,12315,12317,12319,12321],{"class":74,"line":657},[72,12289,605],{"class":100},[72,12291,12292],{"class":508}," score",[72,12294,611],{"class":100},[72,12296,12139],{"class":508},[72,12298,627],{"class":100},[72,12300,12268],{"class":89},[72,12302,476],{"class":446},[72,12304,1330],{"class":89},[72,12306,627],{"class":446},[72,12308,1330],{"class":89},[72,12310,4179],{"class":100},[72,12312,4393],{"class":89},[72,12314,627],{"class":446},[72,12316,1330],{"class":89},[72,12318,712],{"class":446},[72,12320,1887],{"class":78},[72,12322,12323],{"class":82}," 浮点也行\n",[72,12325,12326],{"class":74,"line":691},[72,12327,465],{"emptyLinePlaceholder":464},[72,12329,12330,12332],{"class":74,"line":720},[72,12331,596],{"class":78},[72,12333,12334],{"class":82}," 3) 概率\n",[72,12336,12337,12339,12341,12343,12346,12348,12350,12352,12354,12356,12358,12360,12362,12365,12367],{"class":74,"line":725},[72,12338,1895],{"class":100},[72,12340,12139],{"class":508},[72,12342,627],{"class":100},[72,12344,12345],{"class":89},"random_bool",[72,12347,476],{"class":446},[72,12349,1330],{"class":89},[72,12351,627],{"class":446},[72,12353,4433],{"class":89},[72,12355,682],{"class":446},[72,12357,616],{"class":446},[72,12359,2567],{"class":89},[72,12361,476],{"class":446},[72,12363,12364],{"class":93},"\"30% 概率命中\"",[72,12366,712],{"class":446},[72,12368,1181],{"class":446},[72,12370,12371],{"class":74,"line":733},[72,12372,465],{"emptyLinePlaceholder":464},[72,12374,12375,12377],{"class":74,"line":769},[72,12376,596],{"class":78},[72,12378,12379],{"class":82}," 4) 序列操作\n",[72,12381,12382,12384,12386,12389,12391,12393,12395,12398,12400,12403,12405,12408,12410,12413],{"class":74,"line":774},[72,12383,605],{"class":100},[72,12385,3781],{"class":100},[72,12387,12388],{"class":508}," cards",[72,12390,611],{"class":100},[72,12392,4161],{"class":89},[72,12394,1316],{"class":446},[72,12396,12397],{"class":93},"\"A\"",[72,12399,453],{"class":446},[72,12401,12402],{"class":93}," \"K\"",[72,12404,453],{"class":446},[72,12406,12407],{"class":93}," \"Q\"",[72,12409,453],{"class":446},[72,12411,12412],{"class":93}," \"J\"",[72,12414,12415],{"class":446},"];\n",[72,12417,12418,12421,12423,12426,12428,12430,12432,12434,12436],{"class":74,"line":782},[72,12419,12420],{"class":508},"    cards",[72,12422,627],{"class":100},[72,12424,12425],{"class":89},"shuffle",[72,12427,476],{"class":446},[72,12429,3908],{"class":100},[72,12431,12139],{"class":508},[72,12433,712],{"class":446},[72,12435,2358],{"class":78},[72,12437,12438],{"class":82}," 洗牌\n",[72,12440,12441,12443,12446,12448,12450,12452,12455,12457,12459,12461,12463,12465,12467,12469,12471],{"class":74,"line":797},[72,12442,605],{"class":100},[72,12444,12445],{"class":508}," pick",[72,12447,611],{"class":100},[72,12449,12388],{"class":508},[72,12451,627],{"class":100},[72,12453,12454],{"class":89},"choose",[72,12456,476],{"class":446},[72,12458,3908],{"class":100},[72,12460,12139],{"class":508},[72,12462,682],{"class":446},[72,12464,627],{"class":100},[72,12466,1497],{"class":89},[72,12468,4306],{"class":446},[72,12470,596],{"class":78},[72,12472,12473],{"class":82}," 随机选一个\n",[72,12475,12476,12478,12481,12483,12485,12487,12489,12491,12493,12495,12497,12500,12502,12504,12506,12508,12510,12512,12514,12517,12519,12521],{"class":74,"line":829},[72,12477,605],{"class":100},[72,12479,12480],{"class":508}," picks",[72,12482,512],{"class":100},[72,12484,967],{"class":440},[72,12486,543],{"class":446},[72,12488,3585],{"class":508},[72,12490,1731],{"class":446},[72,12492,611],{"class":100},[72,12494,12388],{"class":508},[72,12496,627],{"class":100},[72,12498,12499],{"class":89},"choose_multiple",[72,12501,476],{"class":446},[72,12503,3908],{"class":100},[72,12505,12139],{"class":508},[72,12507,453],{"class":446},[72,12509,11218],{"class":89},[72,12511,682],{"class":446},[72,12513,627],{"class":100},[72,12515,12516],{"class":89},"collect",[72,12518,4306],{"class":446},[72,12520,552],{"class":78},[72,12522,12523],{"class":82}," 不重复选多个\n",[72,12525,12526],{"class":74,"line":849},[72,12527,465],{"emptyLinePlaceholder":464},[72,12529,12530,12532],{"class":74,"line":858},[72,12531,596],{"class":78},[72,12533,12534],{"class":82}," 5) 随机字符串（验证码、临时文件名）\n",[72,12536,12537,12539,12542,12544,12546,12548,12550,12552,12554,12556,12558,12560,12563,12565,12567,12569,12571,12573,12575,12577,12579,12581,12583,12585,12587,12589,12591,12593,12595,12597],{"class":74,"line":2023},[72,12538,605],{"class":100},[72,12540,12541],{"class":508}," code",[72,12543,512],{"class":100},[72,12545,515],{"class":440},[72,12547,611],{"class":100},[72,12549,3163],{"class":446},[72,12551,1330],{"class":89},[72,12553,4179],{"class":100},[72,12555,12277],{"class":89},[72,12557,682],{"class":446},[72,12559,627],{"class":100},[72,12561,12562],{"class":89},"map",[72,12564,476],{"class":446},[72,12566,7401],{"class":100},[72,12568,3585],{"class":508},[72,12570,7401],{"class":100},[72,12572,12139],{"class":508},[72,12574,627],{"class":100},[72,12576,12268],{"class":89},[72,12578,476],{"class":446},[72,12580,1330],{"class":89},[72,12582,4179],{"class":100},[72,12584,3327],{"class":89},[72,12586,682],{"class":446},[72,12588,627],{"class":100},[72,12590,671],{"class":89},[72,12592,3045],{"class":446},[72,12594,627],{"class":100},[72,12596,12516],{"class":89},[72,12598,1500],{"class":446},[72,12600,12601,12603,12606,12608,12610,12612,12614,12616,12619,12621],{"class":74,"line":2028},[72,12602,605],{"class":100},[72,12604,12605],{"class":508}," token",[72,12607,512],{"class":100},[72,12609,515],{"class":440},[72,12611,611],{"class":100},[72,12613,12106],{"class":440},[72,12615,443],{"class":100},[72,12617,12618],{"class":440},"distr",[72,12620,443],{"class":100},[72,12622,12623],{"class":440},"Alphanumeric\n",[72,12625,12626,12628,12631,12633,12635,12637,12639,12642,12644,12646],{"class":74,"line":2036},[72,12627,1776],{"class":100},[72,12629,12630],{"class":89},"sample_string",[72,12632,476],{"class":446},[72,12634,3908],{"class":100},[72,12636,12139],{"class":508},[72,12638,453],{"class":446},[72,12640,12641],{"class":89}," 16",[72,12643,712],{"class":446},[72,12645,1928],{"class":78},[72,12647,12648],{"class":82}," 字母数字混合 16 位\n",[72,12650,12651],{"class":74,"line":2069},[72,12652,465],{"emptyLinePlaceholder":464},[72,12654,12655,12657,12659,12661,12663,12666,12668,12670,12673,12675,12677,12680,12682,12684,12687,12689,12691,12694,12696,12698,12701,12703,12705,12708,12710,12712,12714,12716,12718,12721,12723,12725],{"class":74,"line":2096},[72,12656,694],{"class":89},[72,12658,476],{"class":446},[72,12660,699],{"class":93},[72,12662,447],{"class":446},[72,12664,12665],{"class":93},"x",[72,12667,707],{"class":446},[72,12669,616],{"class":446},[72,12671,12672],{"class":93},"b",[72,12674,707],{"class":446},[72,12676,616],{"class":446},[72,12678,12679],{"class":93},"f",[72,12681,707],{"class":446},[72,12683,616],{"class":446},[72,12685,12686],{"class":93},"dice",[72,12688,707],{"class":446},[72,12690,616],{"class":446},[72,12692,12693],{"class":93},"score",[72,12695,707],{"class":446},[72,12697,616],{"class":446},[72,12699,12700],{"class":93},"pick",[72,12702,707],{"class":446},[72,12704,616],{"class":446},[72,12706,12707],{"class":93},"picks:?",[72,12709,707],{"class":446},[72,12711,616],{"class":446},[72,12713,53],{"class":93},[72,12715,707],{"class":446},[72,12717,616],{"class":446},[72,12719,12720],{"class":93},"token",[72,12722,707],{"class":446},[72,12724,699],{"class":93},[72,12726,766],{"class":446},[72,12728,12729],{"class":74,"line":2132},[72,12730,465],{"emptyLinePlaceholder":464},[72,12732,12733,12735],{"class":74,"line":2137},[72,12734,596],{"class":78},[72,12736,12737],{"class":82}," 6) 需要可复现（测试、仿真）时，用固定种子的生成器\n",[72,12739,12740,12742,12744,12747,12749,12751,12753,12756,12758,12761,12763,12766,12768,12771,12773,12775],{"class":74,"line":2144},[72,12741,605],{"class":100},[72,12743,3781],{"class":100},[72,12745,12746],{"class":508}," seeded",[72,12748,611],{"class":100},[72,12750,12106],{"class":440},[72,12752,443],{"class":100},[72,12754,12755],{"class":440},"rngs",[72,12757,443],{"class":100},[72,12759,12760],{"class":440},"StdRng",[72,12762,443],{"class":100},[72,12764,12765],{"class":89},"seed_from_u64",[72,12767,476],{"class":446},[72,12769,12770],{"class":89},"42",[72,12772,712],{"class":446},[72,12774,552],{"class":78},[72,12776,12777],{"class":82}," 同种子必然产生同序列\n",[72,12779,12780,12782,12785,12787,12789,12791,12793,12795,12797],{"class":74,"line":4627},[72,12781,605],{"class":100},[72,12783,12784],{"class":508}," _v",[72,12786,512],{"class":100},[72,12788,932],{"class":440},[72,12790,611],{"class":100},[72,12792,12746],{"class":508},[72,12794,627],{"class":100},[72,12796,12187],{"class":89},[72,12798,1500],{"class":446},[72,12800,12801],{"class":74,"line":4650},[72,12802,560],{"class":446},[15,12804,12805,12811,12812,12814,12815,12818,12819,12822],{},[19,12806,12807,12808,12810],{},"为什么默认用 ",[53,12809,12082],{}," 而不是自己播种？"," ",[53,12813,12082],{}," 背后是操作系统熵源，不可预测，适合 token、验证码等安全敏感场景。反过来，测试和仿真实验需要",[19,12816,12817],{},"可复现","，这时必须显式用 ",[53,12820,12821],{},"StdRng::seed_from_u64(固定值)","——两种需求对应两种用法，库把选择权交给你，这正是「默认安全、显式可控」的设计。",[24,12824],{},[27,12826,12828],{"id":12827},"第-10-章-regex正则表达式","第 10 章 regex：正则表达式",[63,12830,12832],{"className":144,"code":12831,"language":146,"meta":68,"style":68},"[dependencies]\nregex = \"1\"\n",[53,12833,12834,12838],{"__ignoreMap":68},[72,12835,12836],{"class":74,"line":75},[72,12837,153],{},[72,12839,12840],{"class":74,"line":86},[72,12841,12842],{},"regex = \"1\"\n",[58,12844,12846],{"id":12845},"_101-基本用法","10.1 基本用法",[63,12848,12850],{"className":428,"code":12849,"language":430,"meta":68,"style":68},"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",[53,12851,12852,12866,12870,12880,12887,12921,12943,12964,12968,12975,13002,13030,13070,13074,13081,13108,13122,13154,13185,13211,13237,13241,13245,13252,13279,13306,13348,13352,13359,13383,13409,13413,13417,13424,13450,13478,13497,13504,13531,13568,13572,13579,13606,13647,13666],{"__ignoreMap":68},[72,12853,12854,12856,12859,12861,12864],{"class":74,"line":75},[72,12855,437],{"class":100},[72,12857,12858],{"class":440}," regex",[72,12860,443],{"class":100},[72,12862,12863],{"class":440},"Regex",[72,12865,688],{"class":446},[72,12867,12868],{"class":74,"line":86},[72,12869,465],{"emptyLinePlaceholder":464},[72,12871,12872,12874,12876,12878],{"class":74,"line":107},[72,12873,569],{"class":100},[72,12875,572],{"class":89},[72,12877,575],{"class":446},[72,12879,503],{"class":446},[72,12881,12882,12884],{"class":74,"line":122},[72,12883,596],{"class":78},[72,12885,12886],{"class":82}," 1) 匹配判断\n",[72,12888,12889,12891,12894,12896,12899,12901,12903,12905,12908,12910,12912,12914,12916,12918],{"class":74,"line":260},[72,12890,605],{"class":100},[72,12892,12893],{"class":508}," re",[72,12895,611],{"class":100},[72,12897,12898],{"class":440}," Regex",[72,12900,443],{"class":100},[72,12902,3877],{"class":89},[72,12904,476],{"class":446},[72,12906,12907],{"class":93},"r\"^\\d{4}-\\d{2}-\\d{2}$\"",[72,12909,682],{"class":446},[72,12911,627],{"class":100},[72,12913,1497],{"class":89},[72,12915,4306],{"class":446},[72,12917,552],{"class":78},[72,12919,12920],{"class":82}," r\"...\" 原始字符串，不用转义反斜杠\n",[72,12922,12923,12926,12928,12931,12933,12936,12938,12941],{"class":74,"line":274},[72,12924,12925],{"class":89},"    assert!",[72,12927,476],{"class":446},[72,12929,12930],{"class":508},"re",[72,12932,627],{"class":100},[72,12934,12935],{"class":89},"is_match",[72,12937,476],{"class":446},[72,12939,12940],{"class":93},"\"2026-09-28\"",[72,12942,4147],{"class":446},[72,12944,12945,12947,12949,12951,12953,12955,12957,12959,12962],{"class":74,"line":287},[72,12946,12925],{"class":89},[72,12948,476],{"class":446},[72,12950,9378],{"class":100},[72,12952,12930],{"class":508},[72,12954,627],{"class":100},[72,12956,12935],{"class":89},[72,12958,476],{"class":446},[72,12960,12961],{"class":93},"\"2026\u002F09\u002F28\"",[72,12963,4147],{"class":446},[72,12965,12966],{"class":74,"line":301},[72,12967,465],{"emptyLinePlaceholder":464},[72,12969,12970,12972],{"class":74,"line":317},[72,12971,596],{"class":78},[72,12973,12974],{"class":82}," 2) 查找\n",[72,12976,12977,12979,12981,12983,12985,12987,12989,12991,12994,12996,12998,13000],{"class":74,"line":331},[72,12978,605],{"class":100},[72,12980,12893],{"class":508},[72,12982,611],{"class":100},[72,12984,12898],{"class":440},[72,12986,443],{"class":100},[72,12988,3877],{"class":89},[72,12990,476],{"class":446},[72,12992,12993],{"class":93},"r\"\\d+\"",[72,12995,682],{"class":446},[72,12997,627],{"class":100},[72,12999,1497],{"class":89},[72,13001,1500],{"class":446},[72,13003,13004,13006,13008,13010,13012,13014,13017,13019,13022,13024,13026,13028],{"class":74,"line":353},[72,13005,605],{"class":100},[72,13007,8172],{"class":508},[72,13009,611],{"class":100},[72,13011,12893],{"class":508},[72,13013,627],{"class":100},[72,13015,13016],{"class":89},"find",[72,13018,476],{"class":446},[72,13020,13021],{"class":93},"\"房间号是 1024 室\"",[72,13023,682],{"class":446},[72,13025,627],{"class":100},[72,13027,1497],{"class":89},[72,13029,1500],{"class":446},[72,13031,13032,13034,13036,13039,13041,13044,13046,13048,13050,13052,13054,13056,13058,13060,13062,13064,13066,13068],{"class":74,"line":602},[72,13033,694],{"class":89},[72,13035,476],{"class":446},[72,13037,13038],{"class":93},"\"找到 '",[72,13040,742],{"class":446},[72,13042,13043],{"class":93},"' 位于 ",[72,13045,447],{"class":446},[72,13047,2108],{"class":93},[72,13049,707],{"class":446},[72,13051,699],{"class":93},[72,13053,453],{"class":446},[72,13055,8172],{"class":508},[72,13057,627],{"class":100},[72,13059,1343],{"class":89},[72,13061,633],{"class":446},[72,13063,8172],{"class":508},[72,13065,627],{"class":100},[72,13067,9389],{"class":89},[72,13069,2020],{"class":446},[72,13071,13072],{"class":74,"line":657},[72,13073,465],{"emptyLinePlaceholder":464},[72,13075,13076,13078],{"class":74,"line":691},[72,13077,596],{"class":78},[72,13079,13080],{"class":82}," 3) 捕获组：提取结构化数据\n",[72,13082,13083,13085,13087,13089,13091,13093,13095,13097,13100,13102,13104,13106],{"class":74,"line":720},[72,13084,605],{"class":100},[72,13086,12893],{"class":508},[72,13088,611],{"class":100},[72,13090,12898],{"class":440},[72,13092,443],{"class":100},[72,13094,3877],{"class":89},[72,13096,476],{"class":446},[72,13098,13099],{"class":93},"r\"(\\w+)@(\\w+\\.\\w+)\"",[72,13101,682],{"class":446},[72,13103,627],{"class":100},[72,13105,1497],{"class":89},[72,13107,1500],{"class":446},[72,13109,13110,13112,13115,13117,13120],{"class":74,"line":725},[72,13111,605],{"class":100},[72,13113,13114],{"class":508}," text",[72,13116,611],{"class":100},[72,13118,13119],{"class":93}," \"联系我: alice@example.com\"",[72,13121,688],{"class":446},[72,13123,13124,13126,13128,13130,13132,13135,13137,13139,13141,13143,13146,13148,13150,13152],{"class":74,"line":733},[72,13125,1895],{"class":100},[72,13127,1299],{"class":100},[72,13129,1302],{"class":440},[72,13131,476],{"class":446},[72,13133,13134],{"class":508},"caps",[72,13136,682],{"class":446},[72,13138,611],{"class":100},[72,13140,12893],{"class":508},[72,13142,627],{"class":100},[72,13144,13145],{"class":89},"captures",[72,13147,476],{"class":446},[72,13149,5031],{"class":508},[72,13151,682],{"class":446},[72,13153,503],{"class":446},[72,13155,13156,13158,13160,13163,13165,13167,13169,13171,13173,13175,13177,13180,13182],{"class":74,"line":769},[72,13157,4522],{"class":89},[72,13159,476],{"class":446},[72,13161,13162],{"class":93},"\"整体: ",[72,13164,742],{"class":446},[72,13166,699],{"class":93},[72,13168,453],{"class":446},[72,13170,1715],{"class":100},[72,13172,13134],{"class":508},[72,13174,1316],{"class":446},[72,13176,1330],{"class":89},[72,13178,13179],{"class":446},"]);",[72,13181,1887],{"class":78},[72,13183,13184],{"class":82}," caps[0] 永远是整个匹配\n",[72,13186,13187,13189,13191,13194,13196,13198,13200,13202,13204,13206,13208],{"class":74,"line":774},[72,13188,4522],{"class":89},[72,13190,476],{"class":446},[72,13192,13193],{"class":93},"\"用户名: ",[72,13195,742],{"class":446},[72,13197,699],{"class":93},[72,13199,453],{"class":446},[72,13201,1715],{"class":100},[72,13203,13134],{"class":508},[72,13205,1316],{"class":446},[72,13207,2062],{"class":89},[72,13209,13210],{"class":446},"]);\n",[72,13212,13213,13215,13217,13220,13222,13224,13226,13228,13230,13232,13235],{"class":74,"line":782},[72,13214,4522],{"class":89},[72,13216,476],{"class":446},[72,13218,13219],{"class":93},"\"域名: ",[72,13221,742],{"class":446},[72,13223,699],{"class":93},[72,13225,453],{"class":446},[72,13227,1715],{"class":100},[72,13229,13134],{"class":508},[72,13231,1316],{"class":446},[72,13233,13234],{"class":89},"2",[72,13236,13210],{"class":446},[72,13238,13239],{"class":74,"line":797},[72,13240,1936],{"class":446},[72,13242,13243],{"class":74,"line":829},[72,13244,465],{"emptyLinePlaceholder":464},[72,13246,13247,13249],{"class":74,"line":849},[72,13248,596],{"class":78},[72,13250,13251],{"class":82}," 命名捕获组：长正则里比数字序号更可读\n",[72,13253,13254,13256,13258,13260,13262,13264,13266,13268,13271,13273,13275,13277],{"class":74,"line":858},[72,13255,605],{"class":100},[72,13257,12893],{"class":508},[72,13259,611],{"class":100},[72,13261,12898],{"class":440},[72,13263,443],{"class":100},[72,13265,3877],{"class":89},[72,13267,476],{"class":446},[72,13269,13270],{"class":93},"r\"(?\u003Cyear>\\d{4})-(?\u003Cmonth>\\d{2})-(?\u003Cday>\\d{2})\"",[72,13272,682],{"class":446},[72,13274,627],{"class":100},[72,13276,1497],{"class":89},[72,13278,1500],{"class":446},[72,13280,13281,13283,13286,13288,13290,13292,13294,13296,13298,13300,13302,13304],{"class":74,"line":2023},[72,13282,605],{"class":100},[72,13284,13285],{"class":508}," caps",[72,13287,611],{"class":100},[72,13289,12893],{"class":508},[72,13291,627],{"class":100},[72,13293,13145],{"class":89},[72,13295,476],{"class":446},[72,13297,12940],{"class":93},[72,13299,682],{"class":446},[72,13301,627],{"class":100},[72,13303,1497],{"class":89},[72,13305,1500],{"class":446},[72,13307,13308,13310,13312,13314,13316,13318,13320,13323,13325,13327,13329,13331,13334,13337,13339,13341,13343,13346],{"class":74,"line":2028},[72,13309,694],{"class":89},[72,13311,476],{"class":446},[72,13313,699],{"class":93},[72,13315,742],{"class":446},[72,13317,11488],{"class":93},[72,13319,742],{"class":446},[72,13321,13322],{"class":93},"日\"",[72,13324,453],{"class":446},[72,13326,1715],{"class":100},[72,13328,13134],{"class":508},[72,13330,1316],{"class":446},[72,13332,13333],{"class":93},"\"month\"",[72,13335,13336],{"class":446},"],",[72,13338,1715],{"class":100},[72,13340,13134],{"class":508},[72,13342,1316],{"class":446},[72,13344,13345],{"class":93},"\"day\"",[72,13347,13210],{"class":446},[72,13349,13350],{"class":74,"line":2036},[72,13351,465],{"emptyLinePlaceholder":464},[72,13353,13354,13356],{"class":74,"line":2069},[72,13355,596],{"class":78},[72,13357,13358],{"class":82}," 4) 迭代所有匹配\n",[72,13360,13361,13363,13365,13367,13369,13371,13374,13376,13379,13381],{"class":74,"line":2096},[72,13362,4169],{"class":100},[72,13364,8172],{"class":508},[72,13366,4174],{"class":100},[72,13368,12893],{"class":508},[72,13370,627],{"class":100},[72,13372,13373],{"class":89},"find_iter",[72,13375,476],{"class":446},[72,13377,13378],{"class":93},"\"2026-09-28 和 2025-01-01\"",[72,13380,682],{"class":446},[72,13382,503],{"class":446},[72,13384,13385,13387,13389,13391,13393,13395,13397,13399,13401,13403,13405,13407],{"class":74,"line":2132},[72,13386,4522],{"class":89},[72,13388,476],{"class":446},[72,13390,699],{"class":93},[72,13392,447],{"class":446},[72,13394,2108],{"class":93},[72,13396,707],{"class":446},[72,13398,699],{"class":93},[72,13400,453],{"class":446},[72,13402,8172],{"class":508},[72,13404,627],{"class":100},[72,13406,1343],{"class":89},[72,13408,2020],{"class":446},[72,13410,13411],{"class":74,"line":2137},[72,13412,1936],{"class":446},[72,13414,13415],{"class":74,"line":2144},[72,13416,465],{"emptyLinePlaceholder":464},[72,13418,13419,13421],{"class":74,"line":4627},[72,13420,596],{"class":78},[72,13422,13423],{"class":82}," 5) 替换\n",[72,13425,13426,13428,13430,13432,13434,13436,13438,13440,13442,13444,13446,13448],{"class":74,"line":4650},[72,13427,605],{"class":100},[72,13429,12893],{"class":508},[72,13431,611],{"class":100},[72,13433,12898],{"class":440},[72,13435,443],{"class":100},[72,13437,3877],{"class":89},[72,13439,476],{"class":446},[72,13441,12993],{"class":93},[72,13443,682],{"class":446},[72,13445,627],{"class":100},[72,13447,1497],{"class":89},[72,13449,1500],{"class":446},[72,13451,13452,13454,13457,13459,13461,13463,13466,13468,13471,13473,13476],{"class":74,"line":4655},[72,13453,605],{"class":100},[72,13455,13456],{"class":508}," masked",[72,13458,611],{"class":100},[72,13460,12893],{"class":508},[72,13462,627],{"class":100},[72,13464,13465],{"class":89},"replace_all",[72,13467,476],{"class":446},[72,13469,13470],{"class":93},"\"电话 13812345678 备用 01012345\"",[72,13472,453],{"class":446},[72,13474,13475],{"class":93}," \"***\"",[72,13477,766],{"class":446},[72,13479,13480,13482,13484,13486,13488,13491,13493,13495],{"class":74,"line":4663},[72,13481,694],{"class":89},[72,13483,476],{"class":446},[72,13485,699],{"class":93},[72,13487,447],{"class":446},[72,13489,13490],{"class":93},"masked",[72,13492,707],{"class":446},[72,13494,699],{"class":93},[72,13496,766],{"class":446},[72,13498,13499,13501],{"class":74,"line":4699},[72,13500,596],{"class":78},[72,13502,13503],{"class":82}," 用捕获组做模板替换：$name 引用命名组\n",[72,13505,13506,13508,13510,13512,13514,13516,13518,13520,13523,13525,13527,13529],{"class":74,"line":4724},[72,13507,605],{"class":100},[72,13509,12893],{"class":508},[72,13511,611],{"class":100},[72,13513,12898],{"class":440},[72,13515,443],{"class":100},[72,13517,3877],{"class":89},[72,13519,476],{"class":446},[72,13521,13522],{"class":93},"r\"(?\u003Cy>\\d{4})-(?\u003Cm>\\d{2})-(?\u003Cd>\\d{2})\"",[72,13524,682],{"class":446},[72,13526,627],{"class":100},[72,13528,1497],{"class":89},[72,13530,1500],{"class":446},[72,13532,13533,13535,13537,13539,13541,13543,13545,13547,13549,13552,13554,13556,13558,13561,13563,13565],{"class":74,"line":4742},[72,13534,694],{"class":89},[72,13536,476],{"class":446},[72,13538,699],{"class":93},[72,13540,742],{"class":446},[72,13542,699],{"class":93},[72,13544,453],{"class":446},[72,13546,12893],{"class":508},[72,13548,627],{"class":100},[72,13550,13551],{"class":89},"replace",[72,13553,476],{"class":446},[72,13555,12940],{"class":93},[72,13557,453],{"class":446},[72,13559,13560],{"class":93}," \"$d\u002F$m\u002F$y\"",[72,13562,2483],{"class":446},[72,13564,552],{"class":78},[72,13566,13567],{"class":82}," 28\u002F09\u002F2026\n",[72,13569,13570],{"class":74,"line":4764},[72,13571,465],{"emptyLinePlaceholder":464},[72,13573,13574,13576],{"class":74,"line":4788},[72,13575,596],{"class":78},[72,13577,13578],{"class":82}," 6) 分割\n",[72,13580,13581,13583,13585,13587,13589,13591,13593,13595,13598,13600,13602,13604],{"class":74,"line":7647},[72,13582,605],{"class":100},[72,13584,12893],{"class":508},[72,13586,611],{"class":100},[72,13588,12898],{"class":440},[72,13590,443],{"class":100},[72,13592,3877],{"class":89},[72,13594,476],{"class":446},[72,13596,13597],{"class":93},"r\"[,，;；\\s]+\"",[72,13599,682],{"class":446},[72,13601,627],{"class":100},[72,13603,1497],{"class":89},[72,13605,1500],{"class":446},[72,13607,13608,13610,13613,13615,13617,13619,13621,13623,13625,13627,13629,13631,13634,13636,13639,13641,13643,13645],{"class":74,"line":7660},[72,13609,605],{"class":100},[72,13611,13612],{"class":508}," parts",[72,13614,512],{"class":100},[72,13616,967],{"class":440},[72,13618,543],{"class":446},[72,13620,676],{"class":100},[72,13622,1718],{"class":440},[72,13624,1731],{"class":446},[72,13626,611],{"class":100},[72,13628,12893],{"class":508},[72,13630,627],{"class":100},[72,13632,13633],{"class":89},"split",[72,13635,476],{"class":446},[72,13637,13638],{"class":93},"\"苹果, 香蕉，橘子；西瓜 桃子\"",[72,13640,682],{"class":446},[72,13642,627],{"class":100},[72,13644,12516],{"class":89},[72,13646,1500],{"class":446},[72,13648,13649,13651,13653,13655,13657,13660,13662,13664],{"class":74,"line":7673},[72,13650,694],{"class":89},[72,13652,476],{"class":446},[72,13654,699],{"class":93},[72,13656,447],{"class":446},[72,13658,13659],{"class":93},"parts:?",[72,13661,707],{"class":446},[72,13663,699],{"class":93},[72,13665,766],{"class":446},[72,13667,13668],{"class":74,"line":7702},[72,13669,560],{"class":446},[58,13671,13673],{"id":13672},"_102-关键性能点正则只编译一次","10.2 关键性能点：正则只编译一次",[63,13675,13677],{"className":428,"code":13676,"language":430,"meta":68,"style":68},"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",[53,13678,13679,13691,13708,13712,13719,13726,13733,13740,13770,13792,13796,13800,13826,13844],{"__ignoreMap":68},[72,13680,13681,13683,13685,13687,13689],{"class":74,"line":75},[72,13682,437],{"class":100},[72,13684,12858],{"class":440},[72,13686,443],{"class":100},[72,13688,12863],{"class":440},[72,13690,688],{"class":446},[72,13692,13693,13695,13697,13699,13701,13703,13706],{"class":74,"line":86},[72,13694,437],{"class":100},[72,13696,1121],{"class":440},[72,13698,443],{"class":100},[72,13700,4028],{"class":440},[72,13702,443],{"class":100},[72,13704,13705],{"class":440},"LazyLock",[72,13707,688],{"class":446},[72,13709,13710],{"class":74,"line":107},[72,13711,465],{"emptyLinePlaceholder":464},[72,13713,13714,13716],{"class":74,"line":122},[72,13715,999],{"class":78},[72,13717,13718],{"class":82}," Regex::new 要做语法解析+编译自动机，成本不低。\n",[72,13720,13721,13723],{"class":74,"line":260},[72,13722,999],{"class":78},[72,13724,13725],{"class":82}," 如果在循环或 handler 里每次 new，是新手最常见的性能坑。\n",[72,13727,13728,13730],{"class":74,"line":274},[72,13729,999],{"class":78},[72,13731,13732],{"class":82}," 为什么用 LazyLock？标准库 1.80+ 内置，首次访问时才初始化，线程安全，\n",[72,13734,13735,13737],{"class":74,"line":287},[72,13736,999],{"class":78},[72,13738,13739],{"class":82}," 替代过去的 lazy_static \u002F once_cell 第三方宏。\n",[72,13741,13742,13744,13747,13750,13752,13754,13756,13758,13760,13762,13764,13766,13768],{"class":74,"line":301},[72,13743,3444],{"class":100},[72,13745,13746],{"class":100}," EMAIL_RE:",[72,13748,13749],{"class":440}," LazyLock",[72,13751,543],{"class":446},[72,13753,12863],{"class":440},[72,13755,1731],{"class":446},[72,13757,611],{"class":100},[72,13759,13749],{"class":440},[72,13761,443],{"class":100},[72,13763,3877],{"class":89},[72,13765,476],{"class":446},[72,13767,1784],{"class":100},[72,13769,503],{"class":446},[72,13771,13772,13775,13777,13779,13781,13784,13786,13788,13790],{"class":74,"line":317},[72,13773,13774],{"class":440},"    Regex",[72,13776,443],{"class":100},[72,13778,3877],{"class":89},[72,13780,476],{"class":446},[72,13782,13783],{"class":93},"r\"^[\\w.+-]+@[\\w-]+\\.[\\w.]+$\"",[72,13785,682],{"class":446},[72,13787,627],{"class":100},[72,13789,1497],{"class":89},[72,13791,3984],{"class":446},[72,13793,13794],{"class":74,"line":331},[72,13795,1441],{"class":446},[72,13797,13798],{"class":74,"line":353},[72,13799,465],{"emptyLinePlaceholder":464},[72,13801,13802,13804,13807,13809,13812,13814,13816,13818,13820,13822,13824],{"class":74,"line":602},[72,13803,569],{"class":100},[72,13805,13806],{"class":89}," validate",[72,13808,476],{"class":446},[72,13810,13811],{"class":508},"email",[72,13813,512],{"class":100},[72,13815,1715],{"class":100},[72,13817,1718],{"class":440},[72,13819,682],{"class":446},[72,13821,578],{"class":100},[72,13823,9615],{"class":440},[72,13825,503],{"class":446},[72,13827,13828,13831,13833,13835,13837,13839,13841],{"class":74,"line":657},[72,13829,13830],{"class":100},"    EMAIL_RE.",[72,13832,12935],{"class":89},[72,13834,476],{"class":446},[72,13836,13811],{"class":508},[72,13838,682],{"class":446},[72,13840,552],{"class":78},[72,13842,13843],{"class":82}," 复用同一份编译结果\n",[72,13845,13846],{"class":74,"line":691},[72,13847,560],{"class":446},[15,13849,13850,13853,13854,13857,13858,13861],{},[19,13851,13852],{},"为什么 regex 是线性时间保证？"," Rust 的 regex 用的是有限自动机而不是回溯引擎，",[19,13855,13856],{},"不存在","某些语言正则的「灾难性回溯」（恶意输入让匹配耗时指数爆炸）。代价是不支持反向引用、环视（lookahead）——需要这些时用 ",[53,13859,13860],{},"fancy-regex"," crate，并接受性能风险。",[24,13863],{},[27,13865,13867],{"id":13866},"第-11-章-itertools迭代器增强","第 11 章 itertools：迭代器增强",[15,13869,13870],{},"标准库的迭代器适配器（map\u002Ffilter\u002Ffold…）很强大，但 itertools 补齐了常用缺失操作。",[63,13872,13874],{"className":144,"code":13873,"language":146,"meta":68,"style":68},"[dependencies]\nitertools = \"0.14\"\n",[53,13875,13876,13880],{"__ignoreMap":68},[72,13877,13878],{"class":74,"line":75},[72,13879,153],{},[72,13881,13882],{"class":74,"line":86},[72,13883,13884],{},"itertools = \"0.14\"\n",[63,13886,13888],{"className":428,"code":13887,"language":430,"meta":68,"style":68},"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",[53,13889,13890,13904,13908,13918,13965,13969,13976,14022,14026,14033,14073,14136,14140,14147,14224,14231,14235,14242,14280,14333,14340,14344,14351,14384,14388,14395,14465,14469,14476,14534,14541,14545,14552,14597,14601,14608,14693,14697,14704,14786,14871],{"__ignoreMap":68},[72,13891,13892,13894,13897,13899,13902],{"class":74,"line":75},[72,13893,437],{"class":100},[72,13895,13896],{"class":440}," itertools",[72,13898,443],{"class":100},[72,13900,13901],{"class":440},"Itertools",[72,13903,688],{"class":446},[72,13905,13906],{"class":74,"line":86},[72,13907,465],{"emptyLinePlaceholder":464},[72,13909,13910,13912,13914,13916],{"class":74,"line":107},[72,13911,569],{"class":100},[72,13913,572],{"class":89},[72,13915,575],{"class":446},[72,13917,503],{"class":446},[72,13919,13920,13922,13924,13926,13928,13930,13932,13934,13936,13938,13940,13942,13944,13946,13949,13951,13954,13956,13958,13960,13963],{"class":74,"line":122},[72,13921,605],{"class":100},[72,13923,802],{"class":508},[72,13925,611],{"class":100},[72,13927,4161],{"class":89},[72,13929,1316],{"class":446},[72,13931,4433],{"class":89},[72,13933,453],{"class":446},[72,13935,4273],{"class":89},[72,13937,453],{"class":446},[72,13939,9361],{"class":89},[72,13941,453],{"class":446},[72,13943,4273],{"class":89},[72,13945,453],{"class":446},[72,13947,13948],{"class":89}," 5",[72,13950,453],{"class":446},[72,13952,13953],{"class":89}," 9",[72,13955,453],{"class":446},[72,13957,11218],{"class":89},[72,13959,453],{"class":446},[72,13961,13962],{"class":89}," 6",[72,13964,12415],{"class":446},[72,13966,13967],{"class":74,"line":260},[72,13968,465],{"emptyLinePlaceholder":464},[72,13970,13971,13973],{"class":74,"line":274},[72,13972,596],{"class":78},[72,13974,13975],{"class":82}," 1) unique：去重（保留顺序）\n",[72,13977,13978,13980,13983,13985,13987,13989,13991,13993,13995,13997,13999,14002,14004,14006,14009,14011,14013,14015,14017,14019],{"class":74,"line":287},[72,13979,605],{"class":100},[72,13981,13982],{"class":508}," uniq",[72,13984,512],{"class":100},[72,13986,967],{"class":440},[72,13988,543],{"class":446},[72,13990,3585],{"class":508},[72,13992,1731],{"class":446},[72,13994,611],{"class":100},[72,13996,802],{"class":508},[72,13998,627],{"class":100},[72,14000,14001],{"class":89},"iter",[72,14003,575],{"class":446},[72,14005,627],{"class":100},[72,14007,14008],{"class":89},"unique",[72,14010,575],{"class":446},[72,14012,627],{"class":100},[72,14014,12516],{"class":89},[72,14016,4306],{"class":446},[72,14018,552],{"class":78},[72,14020,14021],{"class":82}," [3, 1, 4, 5, 9, 2, 6]\n",[72,14023,14024],{"class":74,"line":301},[72,14025,465],{"emptyLinePlaceholder":464},[72,14027,14028,14030],{"class":74,"line":317},[72,14029,596],{"class":78},[72,14031,14032],{"class":82}," 2) sorted \u002F sorted_by：迭代器直接排序\n",[72,14034,14035,14037,14040,14042,14044,14046,14048,14050,14052,14054,14056,14058,14060,14062,14065,14067,14069,14071],{"class":74,"line":331},[72,14036,605],{"class":100},[72,14038,14039],{"class":508}," sorted",[72,14041,512],{"class":100},[72,14043,967],{"class":440},[72,14045,543],{"class":446},[72,14047,3585],{"class":508},[72,14049,1731],{"class":446},[72,14051,611],{"class":100},[72,14053,802],{"class":508},[72,14055,627],{"class":100},[72,14057,14001],{"class":89},[72,14059,575],{"class":446},[72,14061,627],{"class":100},[72,14063,14064],{"class":89},"sorted",[72,14066,575],{"class":446},[72,14068,627],{"class":100},[72,14070,12516],{"class":89},[72,14072,1500],{"class":446},[72,14074,14075,14077,14080,14082,14084,14086,14088,14090,14092,14094,14096,14098,14100,14102,14105,14107,14109,14111,14113,14115,14117,14119,14121,14124,14126,14128,14130,14132,14134],{"class":74,"line":353},[72,14076,605],{"class":100},[72,14078,14079],{"class":508}," desc",[72,14081,512],{"class":100},[72,14083,967],{"class":440},[72,14085,543],{"class":446},[72,14087,3585],{"class":508},[72,14089,1731],{"class":446},[72,14091,611],{"class":100},[72,14093,802],{"class":508},[72,14095,627],{"class":100},[72,14097,14001],{"class":89},[72,14099,575],{"class":446},[72,14101,627],{"class":100},[72,14103,14104],{"class":89},"sorted_by",[72,14106,476],{"class":446},[72,14108,7401],{"class":100},[72,14110,39],{"class":508},[72,14112,453],{"class":446},[72,14114,12201],{"class":508},[72,14116,7401],{"class":100},[72,14118,12201],{"class":508},[72,14120,627],{"class":100},[72,14122,14123],{"class":89},"cmp",[72,14125,476],{"class":446},[72,14127,39],{"class":508},[72,14129,1804],{"class":446},[72,14131,627],{"class":100},[72,14133,12516],{"class":89},[72,14135,1500],{"class":446},[72,14137,14138],{"class":74,"line":602},[72,14139,465],{"emptyLinePlaceholder":464},[72,14141,14142,14144],{"class":74,"line":657},[72,14143,596],{"class":78},[72,14145,14146],{"class":82}," 3) chunk \u002F chunks：分组\n",[72,14148,14149,14151,14154,14156,14158,14160,14162,14164,14166,14169,14171,14173,14175,14177,14179,14181,14184,14186,14188,14190,14192,14195,14197,14199,14201,14203,14205,14208,14210,14212,14214,14216,14218,14220,14222],{"class":74,"line":691},[72,14150,605],{"class":100},[72,14152,14153],{"class":508}," chunks",[72,14155,512],{"class":100},[72,14157,967],{"class":440},[72,14159,543],{"class":446},[72,14161,3067],{"class":440},[72,14163,543],{"class":446},[72,14165,3585],{"class":508},[72,14167,14168],{"class":446},">>",[72,14170,611],{"class":100},[72,14172,802],{"class":508},[72,14174,627],{"class":100},[72,14176,14001],{"class":89},[72,14178,575],{"class":446},[72,14180,627],{"class":100},[72,14182,14183],{"class":89},"chunks",[72,14185,476],{"class":446},[72,14187,4433],{"class":89},[72,14189,682],{"class":446},[72,14191,627],{"class":100},[72,14193,14194],{"class":89},"into_iter",[72,14196,575],{"class":446},[72,14198,627],{"class":100},[72,14200,12562],{"class":89},[72,14202,476],{"class":446},[72,14204,7401],{"class":100},[72,14206,14207],{"class":508},"c",[72,14209,7401],{"class":100},[72,14211,4191],{"class":508},[72,14213,627],{"class":100},[72,14215,12516],{"class":89},[72,14217,3045],{"class":446},[72,14219,627],{"class":100},[72,14221,12516],{"class":89},[72,14223,1500],{"class":446},[72,14225,14226,14228],{"class":74,"line":720},[72,14227,596],{"class":78},[72,14229,14230],{"class":82}," [[3,1,4], [1,5,9], [2,6]]\n",[72,14232,14233],{"class":74,"line":725},[72,14234,465],{"emptyLinePlaceholder":464},[72,14236,14237,14239],{"class":74,"line":733},[72,14238,596],{"class":78},[72,14240,14241],{"class":82}," 4) group_by：按 key 分组（类似 SQL GROUP BY）\n",[72,14243,14244,14246,14249,14251,14253,14255,14258,14260,14263,14265,14268,14270,14273,14275,14278],{"class":74,"line":769},[72,14245,605],{"class":100},[72,14247,14248],{"class":508}," words",[72,14250,611],{"class":100},[72,14252,4161],{"class":89},[72,14254,1316],{"class":446},[72,14256,14257],{"class":93},"\"apple\"",[72,14259,453],{"class":446},[72,14261,14262],{"class":93}," \"avocado\"",[72,14264,453],{"class":446},[72,14266,14267],{"class":93}," \"banana\"",[72,14269,453],{"class":446},[72,14271,14272],{"class":93}," \"blueberry\"",[72,14274,453],{"class":446},[72,14276,14277],{"class":93}," \"cherry\"",[72,14279,12415],{"class":446},[72,14281,14282,14284,14287,14289,14291,14293,14295,14297,14299,14302,14304,14306,14309,14311,14314,14316,14319,14321,14323,14325,14327,14329,14331],{"class":74,"line":774},[72,14283,605],{"class":100},[72,14285,14286],{"class":508}," groups",[72,14288,611],{"class":100},[72,14290,14248],{"class":508},[72,14292,627],{"class":100},[72,14294,14001],{"class":89},[72,14296,575],{"class":446},[72,14298,627],{"class":100},[72,14300,14301],{"class":89},"into_group_map_by",[72,14303,476],{"class":446},[72,14305,7401],{"class":100},[72,14307,14308],{"class":508},"w",[72,14310,7401],{"class":100},[72,14312,14313],{"class":508}," w",[72,14315,627],{"class":100},[72,14317,14318],{"class":89},"chars",[72,14320,575],{"class":446},[72,14322,627],{"class":100},[72,14324,5903],{"class":89},[72,14326,575],{"class":446},[72,14328,627],{"class":100},[72,14330,1497],{"class":89},[72,14332,2020],{"class":446},[72,14334,14335,14337],{"class":74,"line":782},[72,14336,596],{"class":78},[72,14338,14339],{"class":82}," {'a': [apple, avocado], 'b': [banana, blueberry], 'c': [cherry]}\n",[72,14341,14342],{"class":74,"line":797},[72,14343,465],{"emptyLinePlaceholder":464},[72,14345,14346,14348],{"class":74,"line":829},[72,14347,596],{"class":78},[72,14349,14350],{"class":82}," 5) join：迭代器直接拼字符串（比 collect 成 Vec 再 join 少一步）\n",[72,14352,14353,14355,14357,14359,14361,14363,14365,14367,14369,14372,14374,14377,14379,14381],{"class":74,"line":849},[72,14354,605],{"class":100},[72,14356,662],{"class":508},[72,14358,611],{"class":100},[72,14360,14248],{"class":508},[72,14362,627],{"class":100},[72,14364,14001],{"class":89},[72,14366,575],{"class":446},[72,14368,627],{"class":100},[72,14370,14371],{"class":89},"join",[72,14373,476],{"class":446},[72,14375,14376],{"class":93},"\", \"",[72,14378,712],{"class":446},[72,14380,552],{"class":78},[72,14382,14383],{"class":82}," \"apple, avocado, ...\"\n",[72,14385,14386],{"class":74,"line":858},[72,14387,465],{"emptyLinePlaceholder":464},[72,14389,14390,14392],{"class":74,"line":2023},[72,14391,596],{"class":78},[72,14393,14394],{"class":82}," 6) tuple_windows：相邻窗口\n",[72,14396,14397,14399,14402,14404,14406,14408,14411,14413,14415,14417,14419,14421,14423,14425,14428,14430,14432,14434,14436,14438,14440,14442,14444,14446,14448,14450,14452,14454,14457,14459,14461,14463],{"class":74,"line":2028},[72,14398,605],{"class":100},[72,14400,14401],{"class":508}," diffs",[72,14403,512],{"class":100},[72,14405,967],{"class":440},[72,14407,543],{"class":446},[72,14409,14410],{"class":440},"i32",[72,14412,1731],{"class":446},[72,14414,611],{"class":100},[72,14416,802],{"class":508},[72,14418,627],{"class":100},[72,14420,14001],{"class":89},[72,14422,575],{"class":446},[72,14424,627],{"class":100},[72,14426,14427],{"class":89},"tuple_windows",[72,14429,575],{"class":446},[72,14431,627],{"class":100},[72,14433,12562],{"class":89},[72,14435,476],{"class":446},[72,14437,7401],{"class":100},[72,14439,476],{"class":446},[72,14441,39],{"class":508},[72,14443,453],{"class":446},[72,14445,12201],{"class":508},[72,14447,682],{"class":446},[72,14449,7401],{"class":100},[72,14451,12201],{"class":508},[72,14453,11399],{"class":100},[72,14455,14456],{"class":508}," a",[72,14458,682],{"class":446},[72,14460,627],{"class":100},[72,14462,12516],{"class":89},[72,14464,1500],{"class":446},[72,14466,14467],{"class":74,"line":2036},[72,14468,465],{"emptyLinePlaceholder":464},[72,14470,14471,14473],{"class":74,"line":2069},[72,14472,596],{"class":78},[72,14474,14475],{"class":82}," 7) cartesian_product：笛卡尔积\n",[72,14477,14478,14480,14483,14485,14487,14489,14491,14493,14495,14497,14499,14501,14503,14505,14507,14510,14512,14514,14516,14518,14520,14522,14524,14526,14528,14530,14532],{"class":74,"line":2096},[72,14479,605],{"class":100},[72,14481,14482],{"class":508}," pairs",[72,14484,512],{"class":100},[72,14486,967],{"class":440},[72,14488,543],{"class":446},[72,14490,3585],{"class":508},[72,14492,1731],{"class":446},[72,14494,611],{"class":100},[72,14496,3163],{"class":446},[72,14498,2062],{"class":89},[72,14500,9396],{"class":100},[72,14502,13234],{"class":89},[72,14504,682],{"class":446},[72,14506,627],{"class":100},[72,14508,14509],{"class":89},"cartesian_product",[72,14511,476],{"class":446},[72,14513,3441],{"class":446},[72,14515,39],{"class":93},[72,14517,3441],{"class":446},[72,14519,9396],{"class":100},[72,14521,3441],{"class":446},[72,14523,12672],{"class":93},[72,14525,3441],{"class":446},[72,14527,682],{"class":446},[72,14529,627],{"class":100},[72,14531,12516],{"class":89},[72,14533,1500],{"class":446},[72,14535,14536,14538],{"class":74,"line":2132},[72,14537,596],{"class":78},[72,14539,14540],{"class":82}," [(1,'a'), (1,'b'), (2,'a'), (2,'b')]\n",[72,14542,14543],{"class":74,"line":2137},[72,14544,465],{"emptyLinePlaceholder":464},[72,14546,14547,14549],{"class":74,"line":2144},[72,14548,596],{"class":78},[72,14550,14551],{"class":82}," 8) minmax：一次遍历同时拿最小最大值\n",[72,14553,14554,14556,14558,14560,14562,14565,14567,14569,14571,14573,14575,14577,14579,14582,14584,14586,14589,14591,14593,14595],{"class":74,"line":4627},[72,14555,605],{"class":100},[72,14557,3163],{"class":446},[72,14559,5078],{"class":508},[72,14561,453],{"class":446},[72,14563,14564],{"class":508}," max",[72,14566,682],{"class":446},[72,14568,611],{"class":100},[72,14570,802],{"class":508},[72,14572,627],{"class":100},[72,14574,14001],{"class":89},[72,14576,575],{"class":446},[72,14578,627],{"class":100},[72,14580,14581],{"class":89},"minmax",[72,14583,575],{"class":446},[72,14585,627],{"class":100},[72,14587,14588],{"class":89},"into_option",[72,14590,575],{"class":446},[72,14592,627],{"class":100},[72,14594,1497],{"class":89},[72,14596,1500],{"class":446},[72,14598,14599],{"class":74,"line":4650},[72,14600,465],{"emptyLinePlaceholder":464},[72,14602,14603,14605],{"class":74,"line":4655},[72,14604,596],{"class":78},[72,14606,14607],{"class":82}," 9) zip_eq：长度必须相等的 zip（不等会 panic，用于防御长度不匹配的静默截断 bug）\n",[72,14609,14610,14612,14615,14617,14619,14621,14623,14625,14627,14630,14632,14634,14636,14638,14640,14642,14644,14646,14648,14650,14653,14656,14658,14660,14662,14664,14667,14669,14671,14673,14675,14677,14679,14681,14683,14685,14687,14689,14691],{"class":74,"line":4663},[72,14611,605],{"class":100},[72,14613,14614],{"class":508}," zipped",[72,14616,512],{"class":100},[72,14618,967],{"class":440},[72,14620,543],{"class":446},[72,14622,3585],{"class":508},[72,14624,1731],{"class":446},[72,14626,611],{"class":100},[72,14628,14629],{"class":446}," [",[72,14631,2062],{"class":89},[72,14633,453],{"class":446},[72,14635,11218],{"class":89},[72,14637,453],{"class":446},[72,14639,1422],{"class":89},[72,14641,1338],{"class":446},[72,14643,627],{"class":100},[72,14645,14001],{"class":89},[72,14647,575],{"class":446},[72,14649,627],{"class":100},[72,14651,14652],{"class":89},"zip_eq",[72,14654,14655],{"class":446},"([",[72,14657,3441],{"class":446},[72,14659,39],{"class":93},[72,14661,3441],{"class":446},[72,14663,453],{"class":446},[72,14665,14666],{"class":446}," '",[72,14668,12672],{"class":93},[72,14670,3441],{"class":446},[72,14672,453],{"class":446},[72,14674,14666],{"class":446},[72,14676,14207],{"class":93},[72,14678,3441],{"class":446},[72,14680,1338],{"class":446},[72,14682,627],{"class":100},[72,14684,14001],{"class":89},[72,14686,3045],{"class":446},[72,14688,627],{"class":100},[72,14690,12516],{"class":89},[72,14692,1500],{"class":446},[72,14694,14695],{"class":74,"line":4699},[72,14696,465],{"emptyLinePlaceholder":464},[72,14698,14699,14701],{"class":74,"line":4724},[72,14700,596],{"class":78},[72,14702,14703],{"class":82}," 10) format! 风格快速打印\n",[72,14705,14706,14708,14710,14712,14714,14716,14718,14720,14722,14724,14726,14728,14730,14732,14734,14736,14738,14740,14742,14744,14746,14748,14750,14752,14754,14756,14758,14760,14762,14764,14766,14768,14770,14772,14774,14776,14778,14780,14782,14784],{"class":74,"line":4742},[72,14707,694],{"class":89},[72,14709,476],{"class":446},[72,14711,699],{"class":93},[72,14713,447],{"class":446},[72,14715,2108],{"class":93},[72,14717,707],{"class":446},[72,14719,616],{"class":446},[72,14721,2108],{"class":93},[72,14723,707],{"class":446},[72,14725,616],{"class":446},[72,14727,2108],{"class":93},[72,14729,707],{"class":446},[72,14731,616],{"class":446},[72,14733,2108],{"class":93},[72,14735,707],{"class":446},[72,14737,616],{"class":446},[72,14739,2108],{"class":93},[72,14741,707],{"class":446},[72,14743,616],{"class":446},[72,14745,2108],{"class":93},[72,14747,707],{"class":446},[72,14749,616],{"class":446},[72,14751,2108],{"class":93},[72,14753,707],{"class":446},[72,14755,699],{"class":93},[72,14757,453],{"class":446},[72,14759,13982],{"class":508},[72,14761,453],{"class":446},[72,14763,14039],{"class":508},[72,14765,453],{"class":446},[72,14767,14079],{"class":508},[72,14769,453],{"class":446},[72,14771,14153],{"class":508},[72,14773,453],{"class":446},[72,14775,14286],{"class":508},[72,14777,453],{"class":446},[72,14779,14401],{"class":508},[72,14781,453],{"class":446},[72,14783,14482],{"class":508},[72,14785,766],{"class":446},[72,14787,14788,14790,14792,14794,14796,14798,14800,14803,14805,14807,14809,14812,14814,14817,14819,14822,14824,14826,14828,14830,14832,14834,14836,14838,14840,14842,14844,14846,14848,14850,14852,14854,14856,14858,14860,14862,14864,14866,14869],{"class":74,"line":4764},[72,14789,694],{"class":89},[72,14791,476],{"class":446},[72,14793,699],{"class":93},[72,14795,447],{"class":446},[72,14797,704],{"class":93},[72,14799,707],{"class":446},[72,14801,14802],{"class":93}," | min=",[72,14804,447],{"class":446},[72,14806,5078],{"class":93},[72,14808,707],{"class":446},[72,14810,14811],{"class":93}," max=",[72,14813,447],{"class":446},[72,14815,14816],{"class":93},"max",[72,14818,707],{"class":446},[72,14820,14821],{"class":93}," | ",[72,14823,742],{"class":446},[72,14825,699],{"class":93},[72,14827,453],{"class":446},[72,14829,14614],{"class":508},[72,14831,627],{"class":100},[72,14833,14001],{"class":89},[72,14835,575],{"class":446},[72,14837,627],{"class":100},[72,14839,12562],{"class":89},[72,14841,476],{"class":446},[72,14843,7401],{"class":100},[72,14845,476],{"class":446},[72,14847,39],{"class":508},[72,14849,453],{"class":446},[72,14851,3585],{"class":508},[72,14853,682],{"class":446},[72,14855,7401],{"class":100},[72,14857,14456],{"class":508},[72,14859,682],{"class":446},[72,14861,627],{"class":100},[72,14863,14371],{"class":89},[72,14865,476],{"class":446},[72,14867,14868],{"class":93},"\"+\"",[72,14870,4147],{"class":446},[72,14872,14873],{"class":74,"line":4788},[72,14874,560],{"class":446},[15,14876,14877,14880,14881,14884,14885,14888],{},[19,14878,14879],{},"为什么需要 itertools？"," 标准库刻意保持精简，这些操作每个项目都要自己写一遍（而且手写 ",[53,14882,14883],{},"group_by"," 很容易写错）。itertools 的 API 与标准库 Iterator trait 无缝组合——",[53,14886,14887],{},"use itertools::Itertools;"," 一行引入后，所有迭代器自动多出这些方法，零成本、零心智负担。",[24,14890],{},[27,14892,14894],{"id":14893},"第-12-章-rayon一行代码的数据并行","第 12 章 rayon：一行代码的数据并行",[58,14896,14898],{"id":14897},"_121-它解决什么问题","12.1 它解决什么问题",[15,14900,14901,14902,14905,14906,376],{},"CPU 密集型循环想利用多核。传统做法要手动开线程、切分数据、收结果；rayon 只需把 ",[53,14903,14904],{},"iter()"," 换成 ",[53,14907,14908],{},"par_iter()",[63,14910,14912],{"className":144,"code":14911,"language":146,"meta":68,"style":68},"[dependencies]\nrayon = \"1\"\n",[53,14913,14914,14918],{"__ignoreMap":68},[72,14915,14916],{"class":74,"line":75},[72,14917,153],{},[72,14919,14920],{"class":74,"line":86},[72,14921,14922],{},"rayon = \"1\"\n",[63,14924,14926],{"className":428,"code":14925,"language":430,"meta":68,"style":68},"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",[53,14927,14928,14943,14947,14970,14992,15052,15056,15060,15070,15107,15111,15118,15164,15168,15175,15219,15234,15238,15245,15278,15336,15358,15399,15435],{"__ignoreMap":68},[72,14929,14930,14932,14935,14937,14939,14941],{"class":74,"line":75},[72,14931,437],{"class":100},[72,14933,14934],{"class":440}," rayon",[72,14936,443],{"class":100},[72,14938,12111],{"class":440},[72,14940,12114],{"class":100},[72,14942,688],{"class":446},[72,14944,14945],{"class":74,"line":86},[72,14946,465],{"emptyLinePlaceholder":464},[72,14948,14949,14951,14954,14956,14958,14960,14962,14964,14966,14968],{"class":74,"line":107},[72,14950,569],{"class":100},[72,14952,14953],{"class":89}," is_prime",[72,14955,476],{"class":446},[72,14957,4267],{"class":508},[72,14959,512],{"class":100},[72,14961,932],{"class":440},[72,14963,682],{"class":446},[72,14965,578],{"class":100},[72,14967,9615],{"class":440},[72,14969,503],{"class":446},[72,14971,14972,14974,14976,14979,14981,14983,14985,14988,14990],{"class":74,"line":122},[72,14973,1895],{"class":100},[72,14975,4239],{"class":508},[72,14977,14978],{"class":100}," \u003C",[72,14980,11218],{"class":89},[72,14982,616],{"class":446},[72,14984,2464],{"class":100},[72,14986,14987],{"class":89}," false",[72,14989,3030],{"class":446},[72,14991,1181],{"class":446},[72,14993,14994,14997,14999,15001,15004,15006,15009,15011,15013,15015,15018,15020,15022,15024,15026,15028,15031,15033,15035,15037,15039,15041,15043,15045,15048,15050],{"class":74,"line":260},[72,14995,14996],{"class":446},"    (",[72,14998,13234],{"class":89},[72,15000,9396],{"class":100},[72,15002,15003],{"class":446},"((",[72,15005,4267],{"class":508},[72,15007,15008],{"class":100}," as",[72,15010,12226],{"class":440},[72,15012,682],{"class":446},[72,15014,627],{"class":100},[72,15016,15017],{"class":89},"sqrt",[72,15019,575],{"class":446},[72,15021,15008],{"class":100},[72,15023,932],{"class":440},[72,15025,1804],{"class":446},[72,15027,627],{"class":100},[72,15029,15030],{"class":89},"all",[72,15032,476],{"class":446},[72,15034,7401],{"class":100},[72,15036,4460],{"class":508},[72,15038,7401],{"class":100},[72,15040,4239],{"class":508},[72,15042,10479],{"class":100},[72,15044,4424],{"class":508},[72,15046,15047],{"class":100}," !=",[72,15049,1869],{"class":89},[72,15051,1771],{"class":446},[72,15053,15054],{"class":74,"line":274},[72,15055,560],{"class":446},[72,15057,15058],{"class":74,"line":287},[72,15059,465],{"emptyLinePlaceholder":464},[72,15061,15062,15064,15066,15068],{"class":74,"line":301},[72,15063,569],{"class":100},[72,15065,572],{"class":89},[72,15067,575],{"class":446},[72,15069,503],{"class":446},[72,15071,15072,15074,15077,15079,15081,15083,15085,15087,15089,15091,15094,15096,15099,15101,15103,15105],{"class":74,"line":317},[72,15073,605],{"class":100},[72,15075,15076],{"class":508}," nums",[72,15078,512],{"class":100},[72,15080,967],{"class":440},[72,15082,543],{"class":446},[72,15084,2267],{"class":440},[72,15086,1731],{"class":446},[72,15088,611],{"class":100},[72,15090,3163],{"class":446},[72,15092,15093],{"class":89},"1_000_000",[72,15095,4179],{"class":100},[72,15097,15098],{"class":89},"2_000_000",[72,15100,682],{"class":446},[72,15102,627],{"class":100},[72,15104,12516],{"class":89},[72,15106,1500],{"class":446},[72,15108,15109],{"class":74,"line":331},[72,15110,465],{"emptyLinePlaceholder":464},[72,15112,15113,15115],{"class":74,"line":353},[72,15114,596],{"class":78},[72,15116,15117],{"class":82}," 串行版\n",[72,15119,15120,15122,15125,15127,15129,15131,15133,15135,15137,15140,15142,15145,15147,15149,15151,15153,15155,15157,15159,15162],{"class":74,"line":602},[72,15121,605],{"class":100},[72,15123,15124],{"class":508}," count",[72,15126,611],{"class":100},[72,15128,15076],{"class":508},[72,15130,627],{"class":100},[72,15132,14001],{"class":89},[72,15134,575],{"class":446},[72,15136,627],{"class":100},[72,15138,15139],{"class":89},"filter",[72,15141,476],{"class":446},[72,15143,15144],{"class":100},"|&&",[72,15146,4267],{"class":508},[72,15148,7401],{"class":100},[72,15150,14953],{"class":89},[72,15152,476],{"class":446},[72,15154,4267],{"class":508},[72,15156,1804],{"class":446},[72,15158,627],{"class":100},[72,15160,15161],{"class":89},"count",[72,15163,1500],{"class":446},[72,15165,15166],{"class":74,"line":657},[72,15167,465],{"emptyLinePlaceholder":464},[72,15169,15170,15172],{"class":74,"line":691},[72,15171,596],{"class":78},[72,15173,15174],{"class":82}," 并行版：iter -> par_iter，就这一个改动\n",[72,15176,15177,15179,15182,15184,15186,15188,15191,15193,15195,15197,15199,15201,15203,15205,15207,15209,15211,15213,15215,15217],{"class":74,"line":720},[72,15178,605],{"class":100},[72,15180,15181],{"class":508}," count_par",[72,15183,611],{"class":100},[72,15185,15076],{"class":508},[72,15187,627],{"class":100},[72,15189,15190],{"class":89},"par_iter",[72,15192,575],{"class":446},[72,15194,627],{"class":100},[72,15196,15139],{"class":89},[72,15198,476],{"class":446},[72,15200,15144],{"class":100},[72,15202,4267],{"class":508},[72,15204,7401],{"class":100},[72,15206,14953],{"class":89},[72,15208,476],{"class":446},[72,15210,4267],{"class":508},[72,15212,1804],{"class":446},[72,15214,627],{"class":100},[72,15216,15161],{"class":89},[72,15218,1500],{"class":446},[72,15220,15221,15224,15226,15228,15230,15232],{"class":74,"line":725},[72,15222,15223],{"class":89},"    assert_eq!",[72,15225,476],{"class":446},[72,15227,15161],{"class":508},[72,15229,453],{"class":446},[72,15231,15181],{"class":508},[72,15233,766],{"class":446},[72,15235,15236],{"class":74,"line":733},[72,15237,465],{"emptyLinePlaceholder":464},[72,15239,15240,15242],{"class":74,"line":769},[72,15241,596],{"class":78},[72,15243,15244],{"class":82}," 其他常见并行操作，API 与迭代器几乎一致\n",[72,15246,15247,15249,15252,15254,15256,15258,15260,15262,15264,15266,15268,15271,15273,15275],{"class":74,"line":774},[72,15248,605],{"class":100},[72,15250,15251],{"class":508}," sum",[72,15253,512],{"class":100},[72,15255,932],{"class":440},[72,15257,611],{"class":100},[72,15259,15076],{"class":508},[72,15261,627],{"class":100},[72,15263,15190],{"class":89},[72,15265,575],{"class":446},[72,15267,627],{"class":100},[72,15269,15270],{"class":89},"sum",[72,15272,4306],{"class":446},[72,15274,5350],{"class":78},[72,15276,15277],{"class":82}," 并行求和\n",[72,15279,15280,15282,15285,15287,15289,15291,15293,15295,15297,15299,15301,15303,15305,15307,15309,15311,15313,15315,15317,15319,15321,15323,15325,15327,15329,15331,15333],{"class":74,"line":782},[72,15281,605],{"class":100},[72,15283,15284],{"class":508}," squares",[72,15286,512],{"class":100},[72,15288,967],{"class":440},[72,15290,543],{"class":446},[72,15292,2267],{"class":440},[72,15294,1731],{"class":446},[72,15296,611],{"class":100},[72,15298,15076],{"class":508},[72,15300,627],{"class":100},[72,15302,15190],{"class":89},[72,15304,575],{"class":446},[72,15306,627],{"class":100},[72,15308,12562],{"class":89},[72,15310,476],{"class":446},[72,15312,7401],{"class":100},[72,15314,4267],{"class":508},[72,15316,7401],{"class":100},[72,15318,4239],{"class":508},[72,15320,4325],{"class":100},[72,15322,4239],{"class":508},[72,15324,682],{"class":446},[72,15326,627],{"class":100},[72,15328,12516],{"class":89},[72,15330,4306],{"class":446},[72,15332,552],{"class":78},[72,15334,15335],{"class":82}," 并行 map 收集\n",[72,15337,15338,15340,15342,15344,15346,15348,15350,15352,15354,15356],{"class":74,"line":797},[72,15339,605],{"class":100},[72,15341,14564],{"class":508},[72,15343,611],{"class":100},[72,15345,15076],{"class":508},[72,15347,627],{"class":100},[72,15349,15190],{"class":89},[72,15351,575],{"class":446},[72,15353,627],{"class":100},[72,15355,14816],{"class":89},[72,15357,1500],{"class":446},[72,15359,15360,15363,15365,15367,15369,15371,15374,15376,15378,15380,15382,15384,15387,15389,15391,15393,15395,15397],{"class":74,"line":829},[72,15361,15362],{"class":508},"    nums",[72,15364,627],{"class":100},[72,15366,15190],{"class":89},[72,15368,575],{"class":446},[72,15370,627],{"class":100},[72,15372,15373],{"class":89},"for_each",[72,15375,476],{"class":446},[72,15377,7401],{"class":100},[72,15379,4267],{"class":508},[72,15381,7401],{"class":100},[72,15383,616],{"class":446},[72,15385,15386],{"class":82}," \u002F* 只执行不收集 *\u002F",[72,15388,1299],{"class":100},[72,15390,3351],{"class":508},[72,15392,611],{"class":100},[72,15394,4239],{"class":508},[72,15396,3030],{"class":446},[72,15398,4624],{"class":446},[72,15400,15401,15403,15405,15407,15409,15411,15413,15416,15418,15421,15423,15425,15427,15429,15431,15433],{"class":74,"line":849},[72,15402,694],{"class":89},[72,15404,476],{"class":446},[72,15406,699],{"class":93},[72,15408,447],{"class":446},[72,15410,15270],{"class":93},[72,15412,707],{"class":446},[72,15414,15415],{"class":446}," {}",[72,15417,616],{"class":446},[72,15419,15420],{"class":93},"max:?",[72,15422,707],{"class":446},[72,15424,699],{"class":93},[72,15426,453],{"class":446},[72,15428,15284],{"class":508},[72,15430,627],{"class":100},[72,15432,2017],{"class":89},[72,15434,2020],{"class":446},[72,15436,15437],{"class":74,"line":858},[72,15438,560],{"class":446},[15,15440,15441,15444,15445,15448,15449,15452,15453,15455,15456,15459,15460,15463],{},[19,15442,15443],{},"为什么 rayon 敢让你无脑换？"," 两个设计：一是",[19,15446,15447],{},"工作窃取线程池","，任务自动均衡到所有核；二是",[19,15450,15451],{},"复用 Rust 的所有权系统","——",[53,15454,15190],{}," 要求闭包 ",[53,15457,15458],{},"Sync","\u002F",[53,15461,15462],{},"Send","，数据竞争在编译期就是错误，所以并行化不会引入并发 bug。这就是 Rust 名言「无畏并发（fearless concurrency）」的最佳例证。",[58,15465,15467,15468],{"id":15466},"_122-两个独立任务的并行join","12.2 两个独立任务的并行：",[53,15469,14371],{},[63,15471,15473],{"className":428,"code":15472,"language":430,"meta":68,"style":68},"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",[53,15474,15475,15494,15513,15517,15555],{"__ignoreMap":68},[72,15476,15477,15479,15482,15484,15486,15488,15490,15492],{"class":74,"line":75},[72,15478,569],{"class":100},[72,15480,15481],{"class":89}," heavy_a",[72,15483,575],{"class":446},[72,15485,578],{"class":100},[72,15487,932],{"class":440},[72,15489,616],{"class":446},[72,15491,4273],{"class":89},[72,15493,1181],{"class":446},[72,15495,15496,15498,15501,15503,15505,15507,15509,15511],{"class":74,"line":86},[72,15497,569],{"class":100},[72,15499,15500],{"class":89}," heavy_b",[72,15502,575],{"class":446},[72,15504,578],{"class":100},[72,15506,932],{"class":440},[72,15508,616],{"class":446},[72,15510,11218],{"class":89},[72,15512,1181],{"class":446},[72,15514,15515],{"class":74,"line":107},[72,15516,465],{"emptyLinePlaceholder":464},[72,15518,15519,15521,15523,15525,15527,15529,15531,15533,15535,15537,15539,15541,15544,15546,15548,15550,15552],{"class":74,"line":122},[72,15520,1242],{"class":100},[72,15522,3163],{"class":446},[72,15524,39],{"class":508},[72,15526,453],{"class":446},[72,15528,12201],{"class":508},[72,15530,682],{"class":446},[72,15532,611],{"class":100},[72,15534,14934],{"class":440},[72,15536,443],{"class":100},[72,15538,14371],{"class":89},[72,15540,476],{"class":446},[72,15542,15543],{"class":508},"heavy_a",[72,15545,453],{"class":446},[72,15547,15500],{"class":508},[72,15549,712],{"class":446},[72,15551,552],{"class":78},[72,15553,15554],{"class":82}," 两个闭包可能并行执行\n",[72,15556,15557,15559,15561,15563,15565,15567,15569,15571,15573,15575,15577],{"class":74,"line":260},[72,15558,10087],{"class":89},[72,15560,476],{"class":446},[72,15562,699],{"class":93},[72,15564,447],{"class":446},[72,15566,39],{"class":93},[72,15568,707],{"class":446},[72,15570,616],{"class":446},[72,15572,12672],{"class":93},[72,15574,707],{"class":446},[72,15576,699],{"class":93},[72,15578,766],{"class":446},[58,15580,15582],{"id":15581},"_123-何时不该用-rayon","12.3 何时不该用 rayon",[15584,15585,15586,15593,15599],"ul",{},[15587,15588,15589,15592],"li",{},[19,15590,15591],{},"数据量小","（几百个元素）：并行调度开销大于收益，串行更快。",[15587,15594,15595,15598],{},[19,15596,15597],{},"IO 密集","（网络\u002F磁盘）：那不是 CPU 瓶颈，该用 tokio 异步而不是线程并行。",[15587,15600,15601,15602,15605,15606,15609],{},"经验法则：",[19,15603,15604],{},"CPU 密集 + 单机 + 毫秒级以上的循环"," → rayon；",[19,15607,15608],{},"IO 密集 + 高并发连接"," → tokio。",[24,15611],{},[27,15613,15615],{"id":15614},"第-13-章-sqlx编译期检查的-sql","第 13 章 sqlx：编译期检查的 SQL",[58,15617,15619],{"id":15618},"_131-它解决什么问题","13.1 它解决什么问题",[15,15621,15622,15623,15634],{},"Rust 数据库生态两大路线：",[19,15624,15625,15626,15633],{},"ORM（如 diesel、sea-orm）",[19,15627,15628,15629,15632],{},"用代码生成 SQL；",[19,15630,15631],{},"sqlx"," 让你直接写 SQL，但用宏在","编译期连接真实数据库校验语句和类型","——SQL 写错了编译就过不去，兼得原生 SQL 的灵活和类型安全。",[63,15636,15638],{"className":144,"code":15637,"language":146,"meta":68,"style":68},"[dependencies]\nsqlx = { version = \"0.8\", features = [\"runtime-tokio\", \"mysql\", \"chrono\"] }\ntokio = { version = \"1\", features = [\"full\"] }\nchrono = \"0.4\"\n",[53,15639,15640,15644,15649,15653],{"__ignoreMap":68},[72,15641,15642],{"class":74,"line":75},[72,15643,153],{},[72,15645,15646],{"class":74,"line":86},[72,15647,15648],{},"sqlx = { version = \"0.8\", features = [\"runtime-tokio\", \"mysql\", \"chrono\"] }\n",[72,15650,15651],{"class":74,"line":107},[72,15652,163],{},[72,15654,15655],{"class":74,"line":122},[72,15656,15657],{},"chrono = \"0.4\"\n",[58,15659,15661],{"id":15660},"_132-连接池与基本-crud","13.2 连接池与基本 CRUD",[63,15663,15665],{"className":428,"code":15664,"language":430,"meta":68,"style":68},"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",[53,15666,15667,15680,15684,15710,15718,15730,15740,15754,15758,15762,15774,15801,15808,15815,15843,15847,15854,15870,15890,15894,15901,15908,15929,15941,15954,15972,15996,16000,16007,16040,16055,16079,16098,16102,16109,16142,16155,16174,16198,16202,16209,16240,16255,16274,16293,16297,16304,16325,16350,16368,16393,16397,16404,16427,16442,16462,16477,16497,16519,16526],{"__ignoreMap":68},[72,15668,15669,15671,15673,15675,15678],{"class":74,"line":75},[72,15670,437],{"class":100},[72,15672,2392],{"class":440},[72,15674,443],{"class":100},[72,15676,15677],{"class":440},"MySqlPool",[72,15679,688],{"class":446},[72,15681,15682],{"class":74,"line":86},[72,15683,465],{"emptyLinePlaceholder":464},[72,15685,15686,15688,15690,15692,15694,15696,15699,15701,15703,15705,15707],{"class":74,"line":107},[72,15687,470],{"class":446},[72,15689,177],{"class":473},[72,15691,476],{"class":446},[72,15693,15631],{"class":473},[72,15695,443],{"class":100},[72,15697,15698],{"class":440},"FromRow",[72,15700,453],{"class":446},[72,15702,489],{"class":440},[72,15704,916],{"class":446},[72,15706,6469],{"class":78},[72,15708,15709],{"class":82}," FromRow：把查询结果行映射到结构体\n",[72,15711,15712,15714,15716],{"class":74,"line":122},[72,15713,497],{"class":100},[72,15715,500],{"class":440},[72,15717,503],{"class":446},[72,15719,15720,15723,15725,15728],{"class":74,"line":260},[72,15721,15722],{"class":508},"    id",[72,15724,512],{"class":100},[72,15726,15727],{"class":440}," i64",[72,15729,518],{"class":446},[72,15731,15732,15734,15736,15738],{"class":74,"line":274},[72,15733,509],{"class":508},[72,15735,512],{"class":100},[72,15737,515],{"class":440},[72,15739,518],{"class":446},[72,15741,15742,15744,15746,15748,15750,15752],{"class":74,"line":287},[72,15743,535],{"class":508},[72,15745,512],{"class":100},[72,15747,540],{"class":440},[72,15749,543],{"class":446},[72,15751,546],{"class":440},[72,15753,974],{"class":446},[72,15755,15756],{"class":74,"line":301},[72,15757,560],{"class":446},[72,15759,15760],{"class":74,"line":317},[72,15761,465],{"emptyLinePlaceholder":464},[72,15763,15764,15766,15768,15770,15772],{"class":74,"line":331},[72,15765,470],{"class":446},[72,15767,2778],{"class":473},[72,15769,443],{"class":100},[72,15771,2783],{"class":473},[72,15773,2786],{"class":446},[72,15775,15776,15778,15780,15782,15784,15786,15788,15791,15793,15795,15797,15799],{"class":74,"line":353},[72,15777,2791],{"class":100},[72,15779,2794],{"class":100},[72,15781,572],{"class":89},[72,15783,575],{"class":446},[72,15785,578],{"class":100},[72,15787,1662],{"class":440},[72,15789,15790],{"class":446},"\u003C(),",[72,15792,2392],{"class":508},[72,15794,443],{"class":100},[72,15796,2193],{"class":440},[72,15798,1731],{"class":446},[72,15800,503],{"class":446},[72,15802,15803,15805],{"class":74,"line":602},[72,15804,596],{"class":78},[72,15806,15807],{"class":82}," 连接池：为什么必须用池？建一次 TCP+认证握手要几十毫秒，\n",[72,15809,15810,15812],{"class":74,"line":657},[72,15811,596],{"class":78},[72,15813,15814],{"class":82}," 每个请求新建连接数据库会被打爆。池复用连接，自动处理断线重连。\n",[72,15816,15817,15819,15822,15824,15827,15829,15832,15834,15837,15839,15841],{"class":74,"line":691},[72,15818,605],{"class":100},[72,15820,15821],{"class":508}," pool",[72,15823,611],{"class":100},[72,15825,15826],{"class":440}," MySqlPool",[72,15828,443],{"class":100},[72,15830,15831],{"class":89},"connect",[72,15833,476],{"class":446},[72,15835,15836],{"class":93},"\"mysql:\u002F\u002Fuser:pass@localhost\u002Fmydb\"",[72,15838,682],{"class":446},[72,15840,3737],{"class":100},[72,15842,688],{"class":446},[72,15844,15845],{"class":74,"line":720},[72,15846,465],{"emptyLinePlaceholder":464},[72,15848,15849,15851],{"class":74,"line":725},[72,15850,596],{"class":78},[72,15852,15853],{"class":82}," 建表（演示用，生产用 sqlx migrate 管理迁移）\n",[72,15855,15856,15859,15861,15863,15865,15868],{"class":74,"line":733},[72,15857,15858],{"class":440},"    sqlx",[72,15860,443],{"class":100},[72,15862,5530],{"class":89},[72,15864,476],{"class":446},[72,15866,15867],{"class":93},"\"CREATE TABLE IF NOT EXISTS user (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), email VARCHAR(100))\"",[72,15869,1771],{"class":446},[72,15871,15872,15874,15877,15879,15881,15884,15886,15888],{"class":74,"line":769},[72,15873,1776],{"class":100},[72,15875,15876],{"class":89},"execute",[72,15878,476],{"class":446},[72,15880,676],{"class":100},[72,15882,15883],{"class":508},"pool",[72,15885,682],{"class":446},[72,15887,3737],{"class":100},[72,15889,688],{"class":446},[72,15891,15892],{"class":74,"line":774},[72,15893,465],{"emptyLinePlaceholder":464},[72,15895,15896,15898],{"class":74,"line":782},[72,15897,596],{"class":78},[72,15899,15900],{"class":82}," 1) 插入：? 占位符绑定参数，杜绝 SQL 注入\n",[72,15902,15903,15905],{"class":74,"line":797},[72,15904,596],{"class":78},[72,15906,15907],{"class":82}," 为什么不拼接字符串？format!(\"...WHERE id = {id}\") 在 id 来自用户输入时就是注入漏洞\n",[72,15909,15910,15912,15914,15916,15918,15920,15922,15924,15927],{"class":74,"line":829},[72,15911,605],{"class":100},[72,15913,3210],{"class":508},[72,15915,611],{"class":100},[72,15917,2392],{"class":440},[72,15919,443],{"class":100},[72,15921,5530],{"class":89},[72,15923,476],{"class":446},[72,15925,15926],{"class":93},"\"INSERT INTO user (name, email) VALUES (?, ?)\"",[72,15928,1771],{"class":446},[72,15930,15931,15933,15935,15937,15939],{"class":74,"line":849},[72,15932,1776],{"class":100},[72,15934,6775],{"class":89},[72,15936,476],{"class":446},[72,15938,2494],{"class":93},[72,15940,1771],{"class":446},[72,15942,15943,15945,15947,15949,15952],{"class":74,"line":858},[72,15944,1776],{"class":100},[72,15946,6775],{"class":89},[72,15948,476],{"class":446},[72,15950,15951],{"class":93},"\"xm@example.com\"",[72,15953,1771],{"class":446},[72,15955,15956,15958,15960,15962,15964,15966,15968,15970],{"class":74,"line":2023},[72,15957,1776],{"class":100},[72,15959,15876],{"class":89},[72,15961,476],{"class":446},[72,15963,676],{"class":100},[72,15965,15883],{"class":508},[72,15967,682],{"class":446},[72,15969,3737],{"class":100},[72,15971,688],{"class":446},[72,15973,15974,15976,15978,15981,15983,15985,15987,15989,15991,15994],{"class":74,"line":2028},[72,15975,694],{"class":89},[72,15977,476],{"class":446},[72,15979,15980],{"class":93},"\"插入 id = ",[72,15982,742],{"class":446},[72,15984,699],{"class":93},[72,15986,453],{"class":446},[72,15988,3210],{"class":508},[72,15990,627],{"class":100},[72,15992,15993],{"class":89},"last_insert_id",[72,15995,2020],{"class":446},[72,15997,15998],{"class":74,"line":2036},[72,15999,465],{"emptyLinePlaceholder":464},[72,16001,16002,16004],{"class":74,"line":2069},[72,16003,596],{"class":78},[72,16005,16006],{"class":82}," 2) 查询单条映射到结构体\n",[72,16008,16009,16011,16014,16016,16018,16020,16023,16025,16027,16029,16031,16033,16035,16038],{"class":74,"line":2096},[72,16010,605],{"class":100},[72,16012,16013],{"class":508}," user",[72,16015,611],{"class":100},[72,16017,2392],{"class":440},[72,16019,443],{"class":100},[72,16021,16022],{"class":89},"query_as",[72,16024,443],{"class":100},[72,16026,543],{"class":446},[72,16028,3585],{"class":508},[72,16030,453],{"class":446},[72,16032,500],{"class":440},[72,16034,4390],{"class":446},[72,16036,16037],{"class":93},"\"SELECT id, name, email FROM user WHERE id = ?\"",[72,16039,1771],{"class":446},[72,16041,16042,16044,16046,16048,16050,16053],{"class":74,"line":2132},[72,16043,1776],{"class":100},[72,16045,6775],{"class":89},[72,16047,476],{"class":446},[72,16049,2062],{"class":89},[72,16051,16052],{"class":440},"i64",[72,16054,1771],{"class":446},[72,16056,16057,16059,16062,16064,16066,16068,16070,16072,16074,16076],{"class":74,"line":2137},[72,16058,1776],{"class":100},[72,16060,16061],{"class":89},"fetch_one",[72,16063,476],{"class":446},[72,16065,676],{"class":100},[72,16067,15883],{"class":508},[72,16069,682],{"class":446},[72,16071,3737],{"class":100},[72,16073,3030],{"class":446},[72,16075,9016],{"class":78},[72,16077,16078],{"class":82}," 没有行时返回 Err(RowNotFound)\n",[72,16080,16081,16083,16085,16087,16089,16092,16094,16096],{"class":74,"line":2144},[72,16082,694],{"class":89},[72,16084,476],{"class":446},[72,16086,699],{"class":93},[72,16088,447],{"class":446},[72,16090,16091],{"class":93},"user:?",[72,16093,707],{"class":446},[72,16095,699],{"class":93},[72,16097,766],{"class":446},[72,16099,16100],{"class":74,"line":4627},[72,16101,465],{"emptyLinePlaceholder":464},[72,16103,16104,16106],{"class":74,"line":4650},[72,16105,596],{"class":78},[72,16107,16108],{"class":82}," 3) 查询多条\n",[72,16110,16111,16113,16116,16118,16120,16122,16125,16127,16129,16131,16133,16135,16137,16140],{"class":74,"line":4655},[72,16112,605],{"class":100},[72,16114,16115],{"class":508}," users",[72,16117,512],{"class":100},[72,16119,967],{"class":440},[72,16121,543],{"class":446},[72,16123,16124],{"class":440},"User",[72,16126,1731],{"class":446},[72,16128,611],{"class":100},[72,16130,2392],{"class":440},[72,16132,443],{"class":100},[72,16134,16022],{"class":89},[72,16136,476],{"class":446},[72,16138,16139],{"class":93},"\"SELECT id, name, email FROM user WHERE name LIKE ?\"",[72,16141,1771],{"class":446},[72,16143,16144,16146,16148,16150,16153],{"class":74,"line":4663},[72,16145,1776],{"class":100},[72,16147,6775],{"class":89},[72,16149,476],{"class":446},[72,16151,16152],{"class":93},"\"小%\"",[72,16154,1771],{"class":446},[72,16156,16157,16159,16162,16164,16166,16168,16170,16172],{"class":74,"line":4699},[72,16158,1776],{"class":100},[72,16160,16161],{"class":89},"fetch_all",[72,16163,476],{"class":446},[72,16165,676],{"class":100},[72,16167,15883],{"class":508},[72,16169,682],{"class":446},[72,16171,3737],{"class":100},[72,16173,688],{"class":446},[72,16175,16176,16178,16180,16183,16185,16188,16190,16192,16194,16196],{"class":74,"line":4724},[72,16177,694],{"class":89},[72,16179,476],{"class":446},[72,16181,16182],{"class":93},"\"共 ",[72,16184,742],{"class":446},[72,16186,16187],{"class":93}," 人\"",[72,16189,453],{"class":446},[72,16191,16115],{"class":508},[72,16193,627],{"class":100},[72,16195,2017],{"class":89},[72,16197,2020],{"class":446},[72,16199,16200],{"class":74,"line":4742},[72,16201,465],{"emptyLinePlaceholder":464},[72,16203,16204,16206],{"class":74,"line":4764},[72,16205,596],{"class":78},[72,16207,16208],{"class":82}," 4) fetch_optional：行可能不存在时\n",[72,16210,16211,16213,16216,16218,16220,16222,16224,16226,16228,16230,16232,16234,16236,16238],{"class":74,"line":4788},[72,16212,605],{"class":100},[72,16214,16215],{"class":508}," maybe",[72,16217,512],{"class":100},[72,16219,540],{"class":440},[72,16221,543],{"class":446},[72,16223,16124],{"class":440},[72,16225,1731],{"class":446},[72,16227,611],{"class":100},[72,16229,2392],{"class":440},[72,16231,443],{"class":100},[72,16233,16022],{"class":89},[72,16235,476],{"class":446},[72,16237,16037],{"class":93},[72,16239,1771],{"class":446},[72,16241,16242,16244,16246,16248,16251,16253],{"class":74,"line":7647},[72,16243,1776],{"class":100},[72,16245,6775],{"class":89},[72,16247,476],{"class":446},[72,16249,16250],{"class":89},"999",[72,16252,16052],{"class":440},[72,16254,1771],{"class":446},[72,16256,16257,16259,16262,16264,16266,16268,16270,16272],{"class":74,"line":7660},[72,16258,1776],{"class":100},[72,16260,16261],{"class":89},"fetch_optional",[72,16263,476],{"class":446},[72,16265,676],{"class":100},[72,16267,15883],{"class":508},[72,16269,682],{"class":446},[72,16271,3737],{"class":100},[72,16273,688],{"class":446},[72,16275,16276,16278,16280,16282,16284,16287,16289,16291],{"class":74,"line":7673},[72,16277,694],{"class":89},[72,16279,476],{"class":446},[72,16281,699],{"class":93},[72,16283,447],{"class":446},[72,16285,16286],{"class":93},"maybe:?",[72,16288,707],{"class":446},[72,16290,699],{"class":93},[72,16292,766],{"class":446},[72,16294,16295],{"class":74,"line":7702},[72,16296,465],{"emptyLinePlaceholder":464},[72,16298,16299,16301],{"class":74,"line":7720},[72,16300,596],{"class":78},[72,16302,16303],{"class":82}," 5) 更新 \u002F 删除：看受影响行数\n",[72,16305,16306,16308,16310,16312,16314,16316,16318,16320,16323],{"class":74,"line":7726},[72,16307,605],{"class":100},[72,16309,3210],{"class":508},[72,16311,611],{"class":100},[72,16313,2392],{"class":440},[72,16315,443],{"class":100},[72,16317,5530],{"class":89},[72,16319,476],{"class":446},[72,16321,16322],{"class":93},"\"UPDATE user SET email = ? WHERE id = ?\"",[72,16324,1771],{"class":446},[72,16326,16327,16329,16331,16333,16336,16338,16340,16342,16344,16346,16348],{"class":74,"line":7731},[72,16328,1776],{"class":100},[72,16330,6775],{"class":89},[72,16332,476],{"class":446},[72,16334,16335],{"class":93},"\"new@example.com\"",[72,16337,682],{"class":446},[72,16339,627],{"class":100},[72,16341,6775],{"class":89},[72,16343,476],{"class":446},[72,16345,2062],{"class":89},[72,16347,16052],{"class":440},[72,16349,1771],{"class":446},[72,16351,16352,16354,16356,16358,16360,16362,16364,16366],{"class":74,"line":7748},[72,16353,1776],{"class":100},[72,16355,15876],{"class":89},[72,16357,476],{"class":446},[72,16359,676],{"class":100},[72,16361,15883],{"class":508},[72,16363,682],{"class":446},[72,16365,3737],{"class":100},[72,16367,688],{"class":446},[72,16369,16370,16372,16374,16377,16379,16382,16384,16386,16388,16391],{"class":74,"line":7784},[72,16371,694],{"class":89},[72,16373,476],{"class":446},[72,16375,16376],{"class":93},"\"更新 ",[72,16378,742],{"class":446},[72,16380,16381],{"class":93}," 行\"",[72,16383,453],{"class":446},[72,16385,3210],{"class":508},[72,16387,627],{"class":100},[72,16389,16390],{"class":89},"rows_affected",[72,16392,2020],{"class":446},[72,16394,16395],{"class":74,"line":7806},[72,16396,465],{"emptyLinePlaceholder":464},[72,16398,16399,16401],{"class":74,"line":7829},[72,16400,596],{"class":78},[72,16402,16403],{"class":82}," 6) 事务：要么全成，要么全回滚。转账类操作的底线\n",[72,16405,16406,16408,16410,16412,16414,16416,16418,16421,16423,16425],{"class":74,"line":7852},[72,16407,605],{"class":100},[72,16409,3781],{"class":100},[72,16411,4610],{"class":508},[72,16413,611],{"class":100},[72,16415,15821],{"class":508},[72,16417,627],{"class":100},[72,16419,16420],{"class":89},"begin",[72,16422,575],{"class":446},[72,16424,3737],{"class":100},[72,16426,688],{"class":446},[72,16428,16429,16431,16433,16435,16437,16440],{"class":74,"line":7872},[72,16430,15858],{"class":440},[72,16432,443],{"class":100},[72,16434,5530],{"class":89},[72,16436,476],{"class":446},[72,16438,16439],{"class":93},"\"UPDATE account SET balance = balance - 100 WHERE id = 1\"",[72,16441,1771],{"class":446},[72,16443,16444,16446,16448,16450,16452,16454,16456,16458,16460],{"class":74,"line":7877},[72,16445,1776],{"class":100},[72,16447,15876],{"class":89},[72,16449,476],{"class":446},[72,16451,3908],{"class":100},[72,16453,4325],{"class":100},[72,16455,4363],{"class":508},[72,16457,682],{"class":446},[72,16459,3737],{"class":100},[72,16461,688],{"class":446},[72,16463,16464,16466,16468,16470,16472,16475],{"class":74,"line":7912},[72,16465,15858],{"class":440},[72,16467,443],{"class":100},[72,16469,5530],{"class":89},[72,16471,476],{"class":446},[72,16473,16474],{"class":93},"\"UPDATE account SET balance = balance + 100 WHERE id = 2\"",[72,16476,1771],{"class":446},[72,16478,16479,16481,16483,16485,16487,16489,16491,16493,16495],{"class":74,"line":7937},[72,16480,1776],{"class":100},[72,16482,15876],{"class":89},[72,16484,476],{"class":446},[72,16486,3908],{"class":100},[72,16488,4325],{"class":100},[72,16490,4363],{"class":508},[72,16492,682],{"class":446},[72,16494,3737],{"class":100},[72,16496,688],{"class":446},[72,16498,16500,16503,16505,16508,16510,16512,16514,16516],{"class":74,"line":16499},58,[72,16501,16502],{"class":508},"    tx",[72,16504,627],{"class":100},[72,16506,16507],{"class":89},"commit",[72,16509,575],{"class":446},[72,16511,3737],{"class":100},[72,16513,3030],{"class":446},[72,16515,1887],{"class":78},[72,16517,16518],{"class":82}," 若中途 panic 或提前返回，tx 被 drop 自动回滚\n",[72,16520,16522,16524],{"class":74,"line":16521},59,[72,16523,852],{"class":440},[72,16525,855],{"class":446},[72,16527,16529],{"class":74,"line":16528},60,[72,16530,560],{"class":446},[15,16532,16533,16536],{},[19,16534,16535],{},"为什么事务 drop 自动回滚的设计很好？"," 你无法「忘记 rollback」——忘记 commit 就什么都不会发生，资金类系统的失败默认方向是安全的（钱不动），而不是危险的（扣了没加）。这是 Rust RAII 哲学在数据库层的体现。",[58,16538,16540],{"id":16539},"_133-编译期校验宏进阶","13.3 编译期校验宏（进阶）",[63,16542,16544],{"className":428,"code":16543,"language":430,"meta":68,"style":68},"\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",[53,16545,16546,16553,16560,16567],{"__ignoreMap":68},[72,16547,16548,16550],{"class":74,"line":75},[72,16549,999],{"class":78},[72,16551,16552],{"class":82}," 需要编译时能连到数据库（DATABASE_URL 环境变量或 .sqlx 离线数据）\n",[72,16554,16555,16557],{"class":74,"line":86},[72,16556,999],{"class":78},[72,16558,16559],{"class":82}," let user = sqlx::query_as!(User, \"SELECT id, name, email FROM user WHERE id = ?\", 1i64)\n",[72,16561,16562,16564],{"class":74,"line":107},[72,16563,999],{"class":78},[72,16565,16566],{"class":82},"     .fetch_one(&pool).await?;\n",[72,16568,16569,16571],{"class":74,"line":122},[72,16570,999],{"class":78},[72,16572,16573],{"class":82}," 表不存在、列名打错、类型不匹配 → 编译报错，不是运行时 500\n",[12,16575,16576],{},[15,16577,16578,16579,16581,16582,16584,16585,16587],{},"与 axum 配合时，把 ",[53,16580,15677],{}," 放进 ",[53,16583,7290],{},"（见第 5 章），pool 本身是 ",[53,16586,7014],{}," 的廉价句柄。",[24,16589],{},[27,16591,16593],{"id":16592},"第-14-章-高频小工具库速查","第 14 章 高频小工具库速查",[15,16595,16596],{},"按「什么时候会想起它」组织：",[58,16598,16600],{"id":16599},"_141-环境变量配置dotenvy","14.1 环境变量配置：dotenvy",[63,16602,16604],{"className":144,"code":16603,"language":146,"meta":68,"style":68},"dotenvy = \"0.15\"\n",[53,16605,16606],{"__ignoreMap":68},[72,16607,16608],{"class":74,"line":75},[72,16609,16603],{},[63,16611,16613],{"className":428,"code":16612,"language":430,"meta":68,"style":68},"\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",[53,16614,16615,16622,16629,16647],{"__ignoreMap":68},[72,16616,16617,16619],{"class":74,"line":75},[72,16618,999],{"class":78},[72,16620,16621],{"class":82}," 开发时把配置写在项目根目录 .env 文件里（DATABASE_URL=mysql:\u002F\u002F...）\n",[72,16623,16624,16626],{"class":74,"line":86},[72,16625,999],{"class":78},[72,16627,16628],{"class":82}," main 开头一行加载，之后 std::env::var 就能读到\n",[72,16630,16631,16634,16636,16639,16641,16643,16645],{"class":74,"line":107},[72,16632,16633],{"class":440},"dotenvy",[72,16635,443],{"class":100},[72,16637,16638],{"class":89},"dotenv",[72,16640,575],{"class":446},[72,16642,627],{"class":100},[72,16644,7419],{"class":89},[72,16646,1500],{"class":446},[72,16648,16649,16651],{"class":74,"line":122},[72,16650,999],{"class":78},[72,16652,16653],{"class":82}," 为什么用 .ok() 忽略错误？生产环境没有 .env 文件（配置走真实环境变量），不应 panic\n",[58,16655,16657],{"id":16656},"_142-uuiduuid","14.2 UUID：uuid",[63,16659,16661],{"className":144,"code":16660,"language":146,"meta":68,"style":68},"uuid = { version = \"1\", features = [\"v4\", \"serde\"] }\n",[53,16662,16663],{"__ignoreMap":68},[72,16664,16665],{"class":74,"line":75},[72,16666,16660],{},[63,16668,16670],{"className":428,"code":16669,"language":430,"meta":68,"style":68},"let id = uuid::Uuid::new_v4();            \u002F\u002F 随机 UUID，做请求 ID、资源 ID\nprintln!(\"{id}\");                          \u002F\u002F 550e8400-e29b-41d4-a716-446655440000\n",[53,16671,16672,16700],{"__ignoreMap":68},[72,16673,16674,16676,16678,16680,16683,16685,16688,16690,16693,16695,16697],{"class":74,"line":75},[72,16675,1242],{"class":100},[72,16677,2454],{"class":508},[72,16679,611],{"class":100},[72,16681,16682],{"class":440}," uuid",[72,16684,443],{"class":100},[72,16686,16687],{"class":440},"Uuid",[72,16689,443],{"class":100},[72,16691,16692],{"class":89},"new_v4",[72,16694,4306],{"class":446},[72,16696,919],{"class":78},[72,16698,16699],{"class":82}," 随机 UUID，做请求 ID、资源 ID\n",[72,16701,16702,16704,16706,16708,16710,16712,16714,16716,16718,16720],{"class":74,"line":86},[72,16703,10087],{"class":89},[72,16705,476],{"class":446},[72,16707,699],{"class":93},[72,16709,447],{"class":446},[72,16711,2425],{"class":93},[72,16713,707],{"class":446},[72,16715,699],{"class":93},[72,16717,712],{"class":446},[72,16719,11118],{"class":78},[72,16721,16722],{"class":82}," 550e8400-e29b-41d4-a716-446655440000\n",[58,16724,16726],{"id":16725},"_143-全局一次性初始化标准库-lazylock-oncelock","14.3 全局一次性初始化：标准库 LazyLock \u002F OnceLock",[63,16728,16730],{"className":428,"code":16729,"language":430,"meta":68,"style":68},"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",[53,16731,16732,16748,16755,16784,16826],{"__ignoreMap":68},[72,16733,16734,16736,16738,16740,16742,16744,16746],{"class":74,"line":75},[72,16735,437],{"class":100},[72,16737,1121],{"class":440},[72,16739,443],{"class":100},[72,16741,4028],{"class":440},[72,16743,443],{"class":100},[72,16745,13705],{"class":440},[72,16747,688],{"class":446},[72,16749,16750,16752],{"class":74,"line":86},[72,16751,999],{"class":78},[72,16753,16754],{"class":82}," 1.80+ 标准库内置，别再引 lazy_static \u002F once_cell 了\n",[72,16756,16757,16759,16762,16764,16766,16768,16770,16772,16774,16776,16778,16780,16782],{"class":74,"line":107},[72,16758,3444],{"class":100},[72,16760,16761],{"class":100}," CONFIG:",[72,16763,13749],{"class":440},[72,16765,543],{"class":446},[72,16767,546],{"class":440},[72,16769,1731],{"class":446},[72,16771,611],{"class":100},[72,16773,13749],{"class":440},[72,16775,443],{"class":100},[72,16777,3877],{"class":89},[72,16779,476],{"class":446},[72,16781,1784],{"class":100},[72,16783,503],{"class":446},[72,16785,16786,16789,16791,16793,16795,16798,16800,16803,16805,16807,16809,16811,16813,16815,16817,16820,16822,16824],{"class":74,"line":122},[72,16787,16788],{"class":440},"    std",[72,16790,443],{"class":100},[72,16792,10004],{"class":440},[72,16794,443],{"class":100},[72,16796,16797],{"class":89},"var",[72,16799,476],{"class":446},[72,16801,16802],{"class":93},"\"APP_CONFIG\"",[72,16804,682],{"class":446},[72,16806,627],{"class":100},[72,16808,10736],{"class":89},[72,16810,476],{"class":446},[72,16812,7401],{"class":100},[72,16814,3585],{"class":508},[72,16816,7401],{"class":100},[72,16818,16819],{"class":93}," \"default\"",[72,16821,627],{"class":100},[72,16823,630],{"class":89},[72,16825,2501],{"class":446},[72,16827,16828],{"class":74,"line":260},[72,16829,1441],{"class":446},[58,16831,16833],{"id":16832},"_144-编码解码base64-hex","14.4 编码解码：base64 \u002F hex",[63,16835,16837],{"className":144,"code":16836,"language":146,"meta":68,"style":68},"base64 = \"0.22\"\nhex = \"0.4\"\n",[53,16838,16839,16844],{"__ignoreMap":68},[72,16840,16841],{"class":74,"line":75},[72,16842,16843],{},"base64 = \"0.22\"\n",[72,16845,16846],{"class":74,"line":86},[72,16847,16848],{},"hex = \"0.4\"\n",[63,16850,16852],{"className":428,"code":16851,"language":430,"meta":68,"style":68},"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",[53,16853,16854,16889,16916,16949],{"__ignoreMap":68},[72,16855,16856,16858,16861,16863,16865,16868,16870,16872,16874,16877,16879,16882,16884,16887],{"class":74,"line":75},[72,16857,437],{"class":100},[72,16859,16860],{"class":440}," base64",[72,16862,443],{"class":100},[72,16864,447],{"class":446},[72,16866,16867],{"class":440},"Engine",[72,16869,15008],{"class":100},[72,16871,3351],{"class":473},[72,16873,453],{"class":446},[72,16875,16876],{"class":440}," engine",[72,16878,443],{"class":100},[72,16880,16881],{"class":440},"general_purpose",[72,16883,443],{"class":100},[72,16885,16886],{"class":440},"STANDARD",[72,16888,459],{"class":446},[72,16890,16891,16893,16896,16898,16901,16904,16906,16909,16911,16913],{"class":74,"line":86},[72,16892,1242],{"class":100},[72,16894,16895],{"class":508}," b64",[72,16897,611],{"class":100},[72,16899,16900],{"class":100}," STANDARD.",[72,16902,16903],{"class":89},"encode",[72,16905,476],{"class":446},[72,16907,16908],{"class":93},"\"你好\"",[72,16910,712],{"class":446},[72,16912,12282],{"class":78},[72,16914,16915],{"class":82}," 字节 -> base64 字符串\n",[72,16917,16918,16920,16922,16924,16926,16929,16931,16933,16936,16938,16940,16942,16944,16946],{"class":74,"line":107},[72,16919,1242],{"class":100},[72,16921,787],{"class":508},[72,16923,611],{"class":100},[72,16925,16900],{"class":100},[72,16927,16928],{"class":89},"decode",[72,16930,476],{"class":446},[72,16932,676],{"class":100},[72,16934,16935],{"class":508},"b64",[72,16937,682],{"class":446},[72,16939,627],{"class":100},[72,16941,1497],{"class":89},[72,16943,4306],{"class":446},[72,16945,552],{"class":78},[72,16947,16948],{"class":82}," 还原\n",[72,16950,16951,16953,16955,16957,16960,16962,16964,16966,16969,16971,16973],{"class":74,"line":122},[72,16952,1242],{"class":100},[72,16954,4291],{"class":508},[72,16956,611],{"class":100},[72,16958,16959],{"class":440}," hex",[72,16961,443],{"class":100},[72,16963,16903],{"class":89},[72,16965,476],{"class":446},[72,16967,16968],{"class":93},"b\"abc\"",[72,16970,712],{"class":446},[72,16972,1928],{"class":78},[72,16974,16975],{"class":82}," \"616263\"\n",[58,16977,16979],{"id":16978},"_145-遍历目录walkdir","14.5 遍历目录：walkdir",[63,16981,16983],{"className":144,"code":16982,"language":146,"meta":68,"style":68},"walkdir = \"2\"\n",[53,16984,16985],{"__ignoreMap":68},[72,16986,16987],{"class":74,"line":75},[72,16988,16982],{},[63,16990,16992],{"className":428,"code":16991,"language":430,"meta":68,"style":68},"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",[53,16993,16994,17008,17015,17068,17111,17140,17144],{"__ignoreMap":68},[72,16995,16996,16998,17001,17003,17006],{"class":74,"line":75},[72,16997,437],{"class":100},[72,16999,17000],{"class":440}," walkdir",[72,17002,443],{"class":100},[72,17004,17005],{"class":440},"WalkDir",[72,17007,688],{"class":446},[72,17009,17010,17012],{"class":74,"line":86},[72,17011,999],{"class":78},[72,17013,17014],{"class":82}," 递归遍历，自动处理符号链接和排序\n",[72,17016,17017,17020,17023,17025,17028,17030,17032,17034,17037,17039,17041,17043,17045,17047,17050,17052,17054,17056,17058,17060,17062,17064,17066],{"class":74,"line":107},[72,17018,17019],{"class":100},"for",[72,17021,17022],{"class":508}," entry",[72,17024,4174],{"class":100},[72,17026,17027],{"class":440}," WalkDir",[72,17029,443],{"class":100},[72,17031,3877],{"class":89},[72,17033,476],{"class":446},[72,17035,17036],{"class":93},"\".\u002Fsrc\"",[72,17038,682],{"class":446},[72,17040,627],{"class":100},[72,17042,14194],{"class":89},[72,17044,575],{"class":446},[72,17046,627],{"class":100},[72,17048,17049],{"class":89},"filter_map",[72,17051,476],{"class":446},[72,17053,7401],{"class":100},[72,17055,2048],{"class":508},[72,17057,7401],{"class":100},[72,17059,2091],{"class":508},[72,17061,627],{"class":100},[72,17063,7419],{"class":89},[72,17065,3045],{"class":446},[72,17067,503],{"class":446},[72,17069,17070,17072,17074,17076,17078,17080,17082,17085,17087,17089,17092,17094,17096,17098,17100,17102,17104,17107,17109],{"class":74,"line":122},[72,17071,1895],{"class":100},[72,17073,17022],{"class":508},[72,17075,627],{"class":100},[72,17077,1710],{"class":89},[72,17079,575],{"class":446},[72,17081,627],{"class":100},[72,17083,17084],{"class":89},"extension",[72,17086,575],{"class":446},[72,17088,627],{"class":100},[72,17090,17091],{"class":89},"is_some_and",[72,17093,476],{"class":446},[72,17095,7401],{"class":100},[72,17097,2048],{"class":508},[72,17099,7401],{"class":100},[72,17101,2091],{"class":508},[72,17103,2457],{"class":100},[72,17105,17106],{"class":93}," \"rs\"",[72,17108,682],{"class":446},[72,17110,503],{"class":446},[72,17112,17113,17115,17117,17119,17121,17123,17125,17127,17129,17131,17133,17135,17138],{"class":74,"line":260},[72,17114,4522],{"class":89},[72,17116,476],{"class":446},[72,17118,699],{"class":93},[72,17120,742],{"class":446},[72,17122,699],{"class":93},[72,17124,453],{"class":446},[72,17126,17022],{"class":508},[72,17128,627],{"class":100},[72,17130,1710],{"class":89},[72,17132,575],{"class":446},[72,17134,627],{"class":100},[72,17136,17137],{"class":89},"display",[72,17139,2020],{"class":446},[72,17141,17142],{"class":74,"line":274},[72,17143,1936],{"class":446},[72,17145,17146],{"class":74,"line":287},[72,17147,560],{"class":446},[58,17149,17151],{"id":17150},"_146-临时文件tempfile测试常用","14.6 临时文件：tempfile（测试常用）",[63,17153,17155],{"className":144,"code":17154,"language":146,"meta":68,"style":68},"tempfile = \"3\"\n",[53,17156,17157],{"__ignoreMap":68},[72,17158,17159],{"class":74,"line":75},[72,17160,17154],{},[63,17162,17164],{"className":428,"code":17163,"language":430,"meta":68,"style":68},"let mut f = tempfile::NamedTempFile::new().unwrap();\nuse std::io::Write;\nwriteln!(f, \"test\").unwrap();\n\u002F\u002F 为什么用库而不是自己拼 \u002Ftmp\u002Fxxx？自动防重名、权限正确、离开作用域自动删除，测试不残留垃圾\n",[53,17165,17166,17196,17213,17235],{"__ignoreMap":68},[72,17167,17168,17170,17172,17174,17176,17179,17181,17184,17186,17188,17190,17192,17194],{"class":74,"line":75},[72,17169,1242],{"class":100},[72,17171,3781],{"class":100},[72,17173,3784],{"class":508},[72,17175,611],{"class":100},[72,17177,17178],{"class":440}," tempfile",[72,17180,443],{"class":100},[72,17182,17183],{"class":440},"NamedTempFile",[72,17185,443],{"class":100},[72,17187,3877],{"class":89},[72,17189,575],{"class":446},[72,17191,627],{"class":100},[72,17193,1497],{"class":89},[72,17195,1500],{"class":446},[72,17197,17198,17200,17202,17204,17206,17208,17211],{"class":74,"line":86},[72,17199,437],{"class":100},[72,17201,1121],{"class":440},[72,17203,443],{"class":100},[72,17205,3649],{"class":440},[72,17207,443],{"class":100},[72,17209,17210],{"class":440},"Write",[72,17212,688],{"class":446},[72,17214,17215,17218,17220,17222,17224,17227,17229,17231,17233],{"class":74,"line":107},[72,17216,17217],{"class":89},"writeln!",[72,17219,476],{"class":446},[72,17221,12679],{"class":508},[72,17223,453],{"class":446},[72,17225,17226],{"class":93}," \"test\"",[72,17228,682],{"class":446},[72,17230,627],{"class":100},[72,17232,1497],{"class":89},[72,17234,1500],{"class":446},[72,17236,17237,17239],{"class":74,"line":122},[72,17238,999],{"class":78},[72,17240,17241],{"class":82}," 为什么用库而不是自己拼 \u002Ftmp\u002Fxxx？自动防重名、权限正确、离开作用域自动删除，测试不残留垃圾\n",[58,17243,17245],{"id":17244},"_147-csv-读写csv","14.7 CSV 读写：csv",[63,17247,17249],{"className":144,"code":17248,"language":146,"meta":68,"style":68},"csv = \"1\"\n",[53,17250,17251],{"__ignoreMap":68},[72,17252,17253],{"class":74,"line":75},[72,17254,17248],{},[63,17256,17258],{"className":428,"code":17257,"language":430,"meta":68,"style":68},"\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",[53,17259,17260,17267,17304,17324,17340,17362,17366,17373,17380,17384,17391,17426,17454,17479],{"__ignoreMap":68},[72,17261,17262,17264],{"class":74,"line":75},[72,17263,999],{"class":78},[72,17265,17266],{"class":82}," 读取\n",[72,17268,17269,17271,17273,17276,17278,17281,17283,17286,17288,17291,17293,17296,17298,17300,17302],{"class":74,"line":86},[72,17270,1242],{"class":100},[72,17272,3781],{"class":100},[72,17274,17275],{"class":508}," rdr",[72,17277,611],{"class":100},[72,17279,17280],{"class":440}," csv",[72,17282,443],{"class":100},[72,17284,17285],{"class":440},"Reader",[72,17287,443],{"class":100},[72,17289,17290],{"class":89},"from_path",[72,17292,476],{"class":446},[72,17294,17295],{"class":93},"\"data.csv\"",[72,17297,682],{"class":446},[72,17299,627],{"class":100},[72,17301,1497],{"class":89},[72,17303,1500],{"class":446},[72,17305,17306,17308,17311,17313,17315,17317,17320,17322],{"class":74,"line":107},[72,17307,17019],{"class":100},[72,17309,17310],{"class":508}," row",[72,17312,4174],{"class":100},[72,17314,17275],{"class":508},[72,17316,627],{"class":100},[72,17318,17319],{"class":89},"records",[72,17321,575],{"class":446},[72,17323,503],{"class":446},[72,17325,17326,17328,17330,17332,17334,17336,17338],{"class":74,"line":122},[72,17327,605],{"class":100},[72,17329,17310],{"class":508},[72,17331,611],{"class":100},[72,17333,17310],{"class":508},[72,17335,627],{"class":100},[72,17337,1497],{"class":89},[72,17339,1500],{"class":446},[72,17341,17342,17344,17346,17348,17350,17352,17354,17356,17358,17360],{"class":74,"line":260},[72,17343,694],{"class":89},[72,17345,476],{"class":446},[72,17347,699],{"class":93},[72,17349,447],{"class":446},[72,17351,2108],{"class":93},[72,17353,707],{"class":446},[72,17355,699],{"class":93},[72,17357,453],{"class":446},[72,17359,17310],{"class":508},[72,17361,766],{"class":446},[72,17363,17364],{"class":74,"line":274},[72,17365,560],{"class":446},[72,17367,17368,17370],{"class":74,"line":287},[72,17369,999],{"class":78},[72,17371,17372],{"class":82}," 配合 serde 直接反序列化为结构体\n",[72,17374,17375,17377],{"class":74,"line":301},[72,17376,999],{"class":78},[72,17378,17379],{"class":82}," for row in rdr.deserialize::\u003CMyRow>() { let r: MyRow = row.unwrap(); }\n",[72,17381,17382],{"class":74,"line":317},[72,17383,465],{"emptyLinePlaceholder":464},[72,17385,17386,17388],{"class":74,"line":331},[72,17387,999],{"class":78},[72,17389,17390],{"class":82}," 写入\n",[72,17392,17393,17395,17397,17400,17402,17404,17406,17409,17411,17413,17415,17418,17420,17422,17424],{"class":74,"line":353},[72,17394,1242],{"class":100},[72,17396,3781],{"class":100},[72,17398,17399],{"class":508}," wtr",[72,17401,611],{"class":100},[72,17403,17280],{"class":440},[72,17405,443],{"class":100},[72,17407,17408],{"class":440},"Writer",[72,17410,443],{"class":100},[72,17412,17290],{"class":89},[72,17414,476],{"class":446},[72,17416,17417],{"class":93},"\"out.csv\"",[72,17419,682],{"class":446},[72,17421,627],{"class":100},[72,17423,1497],{"class":89},[72,17425,1500],{"class":446},[72,17427,17428,17431,17433,17436,17438,17440,17442,17445,17448,17450,17452],{"class":74,"line":602},[72,17429,17430],{"class":508},"wtr",[72,17432,627],{"class":100},[72,17434,17435],{"class":89},"write_record",[72,17437,14655],{"class":446},[72,17439,1335],{"class":93},[72,17441,453],{"class":446},[72,17443,17444],{"class":93}," \"age\"",[72,17446,17447],{"class":446},"])",[72,17449,627],{"class":100},[72,17451,1497],{"class":89},[72,17453,1500],{"class":446},[72,17455,17456,17458,17460,17462,17464,17466,17468,17471,17473,17475,17477],{"class":74,"line":657},[72,17457,17430],{"class":508},[72,17459,627],{"class":100},[72,17461,17435],{"class":89},[72,17463,14655],{"class":446},[72,17465,2494],{"class":93},[72,17467,453],{"class":446},[72,17469,17470],{"class":93}," \"20\"",[72,17472,17447],{"class":446},[72,17474,627],{"class":100},[72,17476,1497],{"class":89},[72,17478,1500],{"class":446},[72,17480,17481,17483,17485,17487,17489,17491,17493],{"class":74,"line":691},[72,17482,17430],{"class":508},[72,17484,627],{"class":100},[72,17486,3849],{"class":89},[72,17488,575],{"class":446},[72,17490,627],{"class":100},[72,17492,1497],{"class":89},[72,17494,1500],{"class":446},[58,17496,17498],{"id":17497},"_148-进度条indicatifcli-工具体验拉满","14.8 进度条：indicatif（CLI 工具体验拉满）",[63,17500,17502],{"className":144,"code":17501,"language":146,"meta":68,"style":68},"indicatif = \"0.17\"\n",[53,17503,17504],{"__ignoreMap":68},[72,17505,17506],{"class":74,"line":75},[72,17507,17501],{},[63,17509,17511],{"className":428,"code":17510,"language":430,"meta":68,"style":68},"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",[53,17512,17513,17534,17557,17579,17633,17649,17684,17700,17704],{"__ignoreMap":68},[72,17514,17515,17517,17520,17522,17524,17527,17529,17532],{"class":74,"line":75},[72,17516,437],{"class":100},[72,17518,17519],{"class":440}," indicatif",[72,17521,443],{"class":100},[72,17523,447],{"class":446},[72,17525,17526],{"class":440},"ProgressBar",[72,17528,453],{"class":446},[72,17530,17531],{"class":440}," ProgressStyle",[72,17533,459],{"class":446},[72,17535,17536,17538,17541,17543,17546,17548,17550,17552,17555],{"class":74,"line":86},[72,17537,1242],{"class":100},[72,17539,17540],{"class":508}," pb",[72,17542,611],{"class":100},[72,17544,17545],{"class":440}," ProgressBar",[72,17547,443],{"class":100},[72,17549,3877],{"class":89},[72,17551,476],{"class":446},[72,17553,17554],{"class":89},"1000",[72,17556,766],{"class":446},[72,17558,17559,17562,17564,17567,17569,17572,17574,17577],{"class":74,"line":107},[72,17560,17561],{"class":508},"pb",[72,17563,627],{"class":100},[72,17565,17566],{"class":89},"set_style",[72,17568,476],{"class":446},[72,17570,17571],{"class":440},"ProgressStyle",[72,17573,443],{"class":100},[72,17575,17576],{"class":89},"default_bar",[72,17578,3984],{"class":446},[72,17580,17581,17584,17587,17589,17591,17593,17596,17598,17600,17603,17605,17607,17609,17611,17613,17616,17618,17621,17623,17625,17627,17629,17631],{"class":74,"line":122},[72,17582,17583],{"class":100},"    .",[72,17585,17586],{"class":89},"template",[72,17588,476],{"class":446},[72,17590,699],{"class":93},[72,17592,447],{"class":446},[72,17594,17595],{"class":93},"bar:40.cyan\u002Fblue",[72,17597,707],{"class":446},[72,17599,616],{"class":446},[72,17601,17602],{"class":93},"pos",[72,17604,707],{"class":446},[72,17606,15459],{"class":93},[72,17608,447],{"class":446},[72,17610,2017],{"class":93},[72,17612,707],{"class":446},[72,17614,17615],{"class":93}," ETA:",[72,17617,447],{"class":446},[72,17619,17620],{"class":93},"eta",[72,17622,707],{"class":446},[72,17624,699],{"class":93},[72,17626,682],{"class":446},[72,17628,627],{"class":100},[72,17630,1497],{"class":89},[72,17632,2020],{"class":446},[72,17634,17635,17637,17639,17641,17643,17645,17647],{"class":74,"line":260},[72,17636,17019],{"class":100},[72,17638,3351],{"class":508},[72,17640,4174],{"class":100},[72,17642,1869],{"class":89},[72,17644,4179],{"class":100},[72,17646,17554],{"class":89},[72,17648,503],{"class":446},[72,17650,17651,17653,17655,17658,17660,17662,17664,17666,17668,17670,17672,17674,17676,17678,17680,17682],{"class":74,"line":274},[72,17652,16788],{"class":440},[72,17654,443],{"class":100},[72,17656,17657],{"class":440},"thread",[72,17659,443],{"class":100},[72,17661,2851],{"class":89},[72,17663,476],{"class":446},[72,17665,2856],{"class":440},[72,17667,443],{"class":100},[72,17669,2846],{"class":440},[72,17671,443],{"class":100},[72,17673,2865],{"class":440},[72,17675,443],{"class":100},[72,17677,3018],{"class":89},[72,17679,476],{"class":446},[72,17681,13234],{"class":89},[72,17683,4147],{"class":446},[72,17685,17686,17689,17691,17694,17696,17698],{"class":74,"line":287},[72,17687,17688],{"class":508},"    pb",[72,17690,627],{"class":100},[72,17692,17693],{"class":89},"inc",[72,17695,476],{"class":446},[72,17697,2062],{"class":89},[72,17699,766],{"class":446},[72,17701,17702],{"class":74,"line":301},[72,17703,560],{"class":446},[72,17705,17706,17708,17710,17713,17715,17718],{"class":74,"line":317},[72,17707,17561],{"class":508},[72,17709,627],{"class":100},[72,17711,17712],{"class":89},"finish_with_message",[72,17714,476],{"class":446},[72,17716,17717],{"class":93},"\"完成\"",[72,17719,766],{"class":446},[58,17721,17723],{"id":17722},"_149-url-解析url","14.9 URL 解析：url",[63,17725,17727],{"className":144,"code":17726,"language":146,"meta":68,"style":68},"url = \"2\"\n",[53,17728,17729],{"__ignoreMap":68},[72,17730,17731],{"class":74,"line":75},[72,17732,17726],{},[63,17734,17736],{"className":428,"code":17735,"language":430,"meta":68,"style":68},"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",[53,17737,17738,17770,17831,17838],{"__ignoreMap":68},[72,17739,17740,17742,17744,17746,17748,17750,17753,17755,17757,17759,17762,17764,17766,17768],{"class":74,"line":75},[72,17741,1242],{"class":100},[72,17743,608],{"class":508},[72,17745,611],{"class":100},[72,17747,5754],{"class":440},[72,17749,443],{"class":100},[72,17751,17752],{"class":440},"Url",[72,17754,443],{"class":100},[72,17756,9685],{"class":89},[72,17758,476],{"class":446},[72,17760,17761],{"class":93},"\"https:\u002F\u002Fexample.com:8080\u002Fpath?a=1#frag\"",[72,17763,682],{"class":446},[72,17765,627],{"class":100},[72,17767,1497],{"class":89},[72,17769,1500],{"class":446},[72,17771,17772,17774,17776,17778,17780,17782,17784,17786,17788,17790,17792,17795,17797,17799,17801,17803,17805,17807,17810,17812,17814,17816,17818,17821,17823,17825,17827,17829],{"class":74,"line":86},[72,17773,10087],{"class":89},[72,17775,476],{"class":446},[72,17777,699],{"class":93},[72,17779,742],{"class":446},[72,17781,15415],{"class":446},[72,17783,15415],{"class":446},[72,17785,699],{"class":93},[72,17787,453],{"class":446},[72,17789,608],{"class":508},[72,17791,627],{"class":100},[72,17793,17794],{"class":89},"host_str",[72,17796,575],{"class":446},[72,17798,627],{"class":100},[72,17800,1497],{"class":89},[72,17802,633],{"class":446},[72,17804,608],{"class":508},[72,17806,627],{"class":100},[72,17808,17809],{"class":89},"port",[72,17811,575],{"class":446},[72,17813,627],{"class":100},[72,17815,7244],{"class":89},[72,17817,476],{"class":446},[72,17819,17820],{"class":89},"443",[72,17822,2399],{"class":446},[72,17824,608],{"class":508},[72,17826,627],{"class":100},[72,17828,1710],{"class":89},[72,17830,2020],{"class":446},[72,17832,17833,17835],{"class":74,"line":107},[72,17834,999],{"class":78},[72,17836,17837],{"class":82}," 为什么不用字符串切分？URL 有百分号编码、默认端口、相对路径解析等一堆边角，\n",[72,17839,17840,17842],{"class":74,"line":122},[72,17841,999],{"class":78},[72,17843,17844],{"class":82}," 手写必错，用标准库式的解析器一次到位\n",[58,17846,17848],{"id":17847},"_1410-优雅退出ctrlc-tokiosignal","14.10 优雅退出：ctrlc \u002F tokio::signal",[63,17850,17852],{"className":428,"code":17851,"language":430,"meta":68,"style":68},"\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",[53,17853,17854,17861,17871,17888,17923,17927,17934],{"__ignoreMap":68},[72,17855,17856,17858],{"class":74,"line":75},[72,17857,999],{"class":78},[72,17859,17860],{"class":82}," tokio 程序里监听 Ctrl+C，先收尾再退出（关闭连接池、刷日志、保存状态）\n",[72,17862,17863,17865,17867,17869],{"class":74,"line":86},[72,17864,2778],{"class":440},[72,17866,443],{"class":100},[72,17868,3274],{"class":89},[72,17870,503],{"class":446},[72,17872,17873,17876,17878,17881,17883,17885],{"class":74,"line":107},[72,17874,17875],{"class":508},"    _",[72,17877,611],{"class":100},[72,17879,17880],{"class":89}," run_server",[72,17882,575],{"class":446},[72,17884,2564],{"class":100},[72,17886,17887],{"class":446}," {},\n",[72,17889,17890,17892,17894,17896,17898,17901,17903,17906,17908,17910,17912,17914,17916,17919,17921],{"class":74,"line":122},[72,17891,17875],{"class":508},[72,17893,611],{"class":100},[72,17895,114],{"class":440},[72,17897,443],{"class":100},[72,17899,17900],{"class":440},"signal",[72,17902,443],{"class":100},[72,17904,17905],{"class":89},"ctrl_c",[72,17907,575],{"class":446},[72,17909,2564],{"class":100},[72,17911,616],{"class":446},[72,17913,2567],{"class":89},[72,17915,476],{"class":446},[72,17917,17918],{"class":93},"\"收到退出信号，开始收尾\"",[72,17920,712],{"class":446},[72,17922,1181],{"class":446},[72,17924,17925],{"class":74,"line":260},[72,17926,560],{"class":446},[72,17928,17929,17931],{"class":74,"line":274},[72,17930,999],{"class":78},[72,17932,17933],{"class":82}," 为什么不能直接被杀？直接 SIGKILL 可能丢未落库数据、连接不释放。\n",[72,17935,17936,17938],{"class":74,"line":287},[72,17937,999],{"class":78},[72,17939,17940],{"class":82}," 优雅停机（graceful shutdown）是服务端工程素养的基本功。\n",[24,17942],{},[27,17944,17946],{"id":17945},"第-15-章-测试相关的常用工具","第 15 章 测试相关的常用工具",[58,17948,17950],{"id":17949},"_151-内置测试框架回顾","15.1 内置测试框架回顾",[63,17952,17954],{"className":428,"code":17953,"language":430,"meta":68,"style":68},"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",[53,17955,17956,17994,17998,18017,18026,18037,18041,18049,18060,18089,18112,18147,18151,18155,18163,18187,18210,18214,18222,18248,18297,18301,18305,18313,18332,18343],{"__ignoreMap":68},[72,17957,17958,17960,17962,17964,17966,17968,17970,17972,17974,17976,17978,17980,17982,17984,17986,17988,17990,17992],{"class":74,"line":75},[72,17959,569],{"class":100},[72,17961,94],{"class":89},[72,17963,476],{"class":446},[72,17965,39],{"class":508},[72,17967,512],{"class":100},[72,17969,1844],{"class":440},[72,17971,453],{"class":446},[72,17973,12201],{"class":508},[72,17975,512],{"class":100},[72,17977,1844],{"class":440},[72,17979,682],{"class":446},[72,17981,578],{"class":100},[72,17983,1844],{"class":440},[72,17985,616],{"class":446},[72,17987,14456],{"class":508},[72,17989,7363],{"class":100},[72,17991,12201],{"class":508},[72,17993,1181],{"class":446},[72,17995,17996],{"class":74,"line":86},[72,17997,465],{"emptyLinePlaceholder":464},[72,17999,18000,18002,18005,18007,18010,18012,18014],{"class":74,"line":107},[72,18001,470],{"class":446},[72,18003,18004],{"class":473},"cfg",[72,18006,476],{"class":446},[72,18008,18009],{"class":473},"test",[72,18011,916],{"class":446},[72,18013,919],{"class":78},[72,18015,18016],{"class":82}," 只在 cargo test 时编译，不进正式产物\n",[72,18018,18019,18021,18024],{"class":74,"line":122},[72,18020,11705],{"class":100},[72,18022,18023],{"class":89}," tests",[72,18025,503],{"class":446},[72,18027,18028,18030,18033,18035],{"class":74,"line":260},[72,18029,11715],{"class":100},[72,18031,18032],{"class":508}," super",[72,18034,12114],{"class":100},[72,18036,688],{"class":446},[72,18038,18039],{"class":74,"line":274},[72,18040,465],{"emptyLinePlaceholder":464},[72,18042,18043,18045,18047],{"class":74,"line":287},[72,18044,899],{"class":446},[72,18046,18009],{"class":473},[72,18048,2786],{"class":446},[72,18050,18051,18053,18056,18058],{"class":74,"line":301},[72,18052,8102],{"class":100},[72,18054,18055],{"class":89}," test_add",[72,18057,575],{"class":446},[72,18059,503],{"class":446},[72,18061,18062,18065,18067,18070,18072,18074,18076,18078,18080,18082,18084,18086],{"class":74,"line":317},[72,18063,18064],{"class":89},"        assert_eq!",[72,18066,476],{"class":446},[72,18068,18069],{"class":89},"add",[72,18071,476],{"class":446},[72,18073,2062],{"class":89},[72,18075,453],{"class":446},[72,18077,11218],{"class":89},[72,18079,2399],{"class":446},[72,18081,1422],{"class":89},[72,18083,712],{"class":446},[72,18085,919],{"class":78},[72,18087,18088],{"class":82}," 相等断言，失败时打印两边值\n",[72,18090,18091,18094,18096,18098,18100,18102,18104,18106,18108,18110],{"class":74,"line":331},[72,18092,18093],{"class":89},"        assert_ne!",[72,18095,476],{"class":446},[72,18097,18069],{"class":89},[72,18099,476],{"class":446},[72,18101,2062],{"class":89},[72,18103,453],{"class":446},[72,18105,11218],{"class":89},[72,18107,2399],{"class":446},[72,18109,9361],{"class":89},[72,18111,766],{"class":446},[72,18113,18114,18117,18119,18121,18123,18125,18127,18129,18131,18133,18135,18137,18140,18142,18144],{"class":74,"line":353},[72,18115,18116],{"class":89},"        assert!",[72,18118,476],{"class":446},[72,18120,18069],{"class":89},[72,18122,476],{"class":446},[72,18124,13234],{"class":89},[72,18126,453],{"class":446},[72,18128,11218],{"class":89},[72,18130,682],{"class":446},[72,18132,1900],{"class":446},[72,18134,1422],{"class":89},[72,18136,453],{"class":446},[72,18138,18139],{"class":93}," \"自定义失败信息\"",[72,18141,712],{"class":446},[72,18143,552],{"class":78},[72,18145,18146],{"class":82}," 第二参数是失败时的提示\n",[72,18148,18149],{"class":74,"line":602},[72,18150,1936],{"class":446},[72,18152,18153],{"class":74,"line":657},[72,18154,465],{"emptyLinePlaceholder":464},[72,18156,18157,18159,18161],{"class":74,"line":691},[72,18158,899],{"class":446},[72,18160,18009],{"class":473},[72,18162,2786],{"class":446},[72,18164,18165,18167,18170,18172,18175,18177,18180,18182,18184],{"class":74,"line":720},[72,18166,899],{"class":446},[72,18168,18169],{"class":473},"should_panic",[72,18171,476],{"class":446},[72,18173,18174],{"class":473},"expected ",[72,18176,910],{"class":100},[72,18178,18179],{"class":93}," \"除以零\"",[72,18181,916],{"class":446},[72,18183,9016],{"class":78},[72,18185,18186],{"class":82}," 断言「必须 panic 且信息包含指定文本」\n",[72,18188,18189,18191,18194,18196,18198,18201,18203,18206,18208],{"class":74,"line":725},[72,18190,8102],{"class":100},[72,18192,18193],{"class":89}," test_div_zero",[72,18195,575],{"class":446},[72,18197,616],{"class":446},[72,18199,18200],{"class":89}," panic!",[72,18202,476],{"class":446},[72,18204,18205],{"class":93},"\"除以零\"",[72,18207,712],{"class":446},[72,18209,1181],{"class":446},[72,18211,18212],{"class":74,"line":733},[72,18213,465],{"emptyLinePlaceholder":464},[72,18215,18216,18218,18220],{"class":74,"line":769},[72,18217,899],{"class":446},[72,18219,18009],{"class":473},[72,18221,2786],{"class":446},[72,18223,18224,18226,18229,18231,18233,18235,18237,18239,18241,18243,18245],{"class":74,"line":774},[72,18225,8102],{"class":100},[72,18227,18228],{"class":89}," test_result",[72,18230,575],{"class":446},[72,18232,578],{"class":100},[72,18234,1662],{"class":440},[72,18236,15790],{"class":446},[72,18238,515],{"class":440},[72,18240,1731],{"class":446},[72,18242,616],{"class":446},[72,18244,552],{"class":78},[72,18246,18247],{"class":82}," 返回 Result 的测试：Err 即失败，能用 ?\n",[72,18249,18250,18253,18255,18257,18259,18261,18263,18265,18267,18269,18271,18273,18276,18278,18280,18282,18284,18286,18289,18291,18293,18295],{"class":74,"line":782},[72,18251,18252],{"class":100},"        if",[72,18254,94],{"class":89},[72,18256,476],{"class":446},[72,18258,13234],{"class":89},[72,18260,453],{"class":446},[72,18262,11218],{"class":89},[72,18264,682],{"class":446},[72,18266,2457],{"class":100},[72,18268,9361],{"class":89},[72,18270,616],{"class":446},[72,18272,3033],{"class":440},[72,18274,18275],{"class":446},"(())",[72,18277,6466],{"class":446},[72,18279,7589],{"class":100},[72,18281,616],{"class":446},[72,18283,2043],{"class":440},[72,18285,476],{"class":446},[72,18287,18288],{"class":93},"\"算错了\"",[72,18290,627],{"class":100},[72,18292,630],{"class":89},[72,18294,3045],{"class":446},[72,18296,1181],{"class":446},[72,18298,18299],{"class":74,"line":797},[72,18300,1936],{"class":446},[72,18302,18303],{"class":74,"line":829},[72,18304,465],{"emptyLinePlaceholder":464},[72,18306,18307,18309,18311],{"class":74,"line":849},[72,18308,899],{"class":446},[72,18310,18009],{"class":473},[72,18312,2786],{"class":446},[72,18314,18315,18317,18320,18322,18325,18327,18329],{"class":74,"line":858},[72,18316,899],{"class":446},[72,18318,18319],{"class":473},"ignore ",[72,18321,910],{"class":100},[72,18323,18324],{"class":93}," \"需要真实数据库，CI 里单独跑\"",[72,18326,1338],{"class":446},[72,18328,552],{"class":78},[72,18330,18331],{"class":82}," cargo test -- --ignored 才运行\n",[72,18333,18334,18336,18339,18341],{"class":74,"line":2023},[72,18335,8102],{"class":100},[72,18337,18338],{"class":89}," test_integration",[72,18340,575],{"class":446},[72,18342,2630],{"class":446},[72,18344,18345],{"class":74,"line":2028},[72,18346,560],{"class":446},[58,18348,18350],{"id":18349},"_152-浮点数比较approx","15.2 浮点数比较：approx",[63,18352,18354],{"className":144,"code":18353,"language":146,"meta":68,"style":68},"[dev-dependencies]       # 只在测试时编译的依赖放这里\napprox = \"0.5\"\n",[53,18355,18356,18361],{"__ignoreMap":68},[72,18357,18358],{"class":74,"line":75},[72,18359,18360],{},"[dev-dependencies]       # 只在测试时编译的依赖放这里\n",[72,18362,18363],{"class":74,"line":86},[72,18364,18365],{},"approx = \"0.5\"\n",[63,18367,18369],{"className":428,"code":18368,"language":430,"meta":68,"style":68},"\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",[53,18370,18371,18378],{"__ignoreMap":68},[72,18372,18373,18375],{"class":74,"line":75},[72,18374,999],{"class":78},[72,18376,18377],{"class":82}," 为什么不直接 assert_eq!(0.1 + 0.2, 0.3)？浮点精度问题，0.1+0.2 ≠ 0.3 是成立的！\n",[72,18379,18380,18383,18385,18387,18389,18391,18393,18395,18397,18399,18401,18403,18405,18407,18409,18412,18414,18416,18419,18421,18423,18425],{"class":74,"line":86},[72,18381,18382],{"class":89},"assert_relative_eq!",[72,18384,476],{"class":446},[72,18386,1330],{"class":89},[72,18388,627],{"class":446},[72,18390,2062],{"class":89},[72,18392,7363],{"class":100},[72,18394,1869],{"class":89},[72,18396,627],{"class":446},[72,18398,13234],{"class":89},[72,18400,453],{"class":446},[72,18402,1869],{"class":89},[72,18404,627],{"class":446},[72,18406,4433],{"class":89},[72,18408,453],{"class":446},[72,18410,18411],{"class":508}," epsilon",[72,18413,611],{"class":100},[72,18415,4273],{"class":89},[72,18417,18418],{"class":100},"e-",[72,18420,3327],{"class":89},[72,18422,712],{"class":446},[72,18424,552],{"class":78},[72,18426,18427],{"class":82}," 需要 use approx::assert_relative_eq;\n",[58,18429,18431],{"id":18430},"_153-异步测试直接用-tokio-宏","15.3 异步测试：直接用 tokio 宏",[63,18433,18435],{"className":428,"code":18434,"language":430,"meta":68,"style":68},"#[tokio::test]           \u002F\u002F 自动给你建运行时\nasync fn test_async() {\n    let resp = async { 42 }.await;\n    assert_eq!(resp, 42);\n}\n",[53,18436,18437,18455,18468,18489,18504],{"__ignoreMap":68},[72,18438,18439,18441,18443,18445,18447,18449,18452],{"class":74,"line":75},[72,18440,470],{"class":446},[72,18442,2778],{"class":473},[72,18444,443],{"class":100},[72,18446,18009],{"class":473},[72,18448,1338],{"class":446},[72,18450,18451],{"class":78},"           \u002F\u002F",[72,18453,18454],{"class":82}," 自动给你建运行时\n",[72,18456,18457,18459,18461,18464,18466],{"class":74,"line":86},[72,18458,2791],{"class":100},[72,18460,2794],{"class":100},[72,18462,18463],{"class":89}," test_async",[72,18465,575],{"class":446},[72,18467,503],{"class":446},[72,18469,18470,18472,18474,18476,18478,18480,18483,18485,18487],{"class":74,"line":107},[72,18471,605],{"class":100},[72,18473,5384],{"class":508},[72,18475,611],{"class":100},[72,18477,8785],{"class":100},[72,18479,616],{"class":446},[72,18481,18482],{"class":89}," 42",[72,18484,6466],{"class":446},[72,18486,2879],{"class":100},[72,18488,688],{"class":446},[72,18490,18491,18493,18495,18498,18500,18502],{"class":74,"line":122},[72,18492,15223],{"class":89},[72,18494,476],{"class":446},[72,18496,18497],{"class":508},"resp",[72,18499,453],{"class":446},[72,18501,18482],{"class":89},[72,18503,766],{"class":446},[72,18505,18506],{"class":74,"line":260},[72,18507,560],{"class":446},[58,18509,18511],{"id":18510},"_154-mockmockall简例","15.4 Mock：mockall（简例）",[63,18513,18515],{"className":144,"code":18514,"language":146,"meta":68,"style":68},"[dev-dependencies]\nmockall = \"0.13\"\n",[53,18516,18517,18522],{"__ignoreMap":68},[72,18518,18519],{"class":74,"line":75},[72,18520,18521],{},"[dev-dependencies]\n",[72,18523,18524],{"class":74,"line":86},[72,18525,18526],{},"mockall = \"0.13\"\n",[63,18528,18530],{"className":428,"code":18529,"language":430,"meta":68,"style":68},"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",[53,18531,18532,18555,18559,18573,18616,18620,18654,18684,18688,18692,18700,18711,18731,18748,18773,18786,18814,18840,18847,18854],{"__ignoreMap":68},[72,18533,18534,18536,18539,18541,18543,18546,18548,18551,18553],{"class":74,"line":75},[72,18535,437],{"class":100},[72,18537,18538],{"class":440}," mockall",[72,18540,443],{"class":100},[72,18542,447],{"class":446},[72,18544,18545],{"class":473},"automock",[72,18547,453],{"class":446},[72,18549,18550],{"class":440}," predicate",[72,18552,12114],{"class":100},[72,18554,459],{"class":446},[72,18556,18557],{"class":74,"line":86},[72,18558,465],{"emptyLinePlaceholder":464},[72,18560,18561,18563,18565,18567,18570],{"class":74,"line":107},[72,18562,470],{"class":446},[72,18564,18545],{"class":473},[72,18566,1338],{"class":446},[72,18568,18569],{"class":78},"                        \u002F\u002F",[72,18571,18572],{"class":82}," 自动生成 MockDb，可预设每个方法的返回值与调用次数\n",[72,18574,18575,18578,18581,18583,18585,18587,18589,18591,18593,18595,18597,18599,18601,18603,18605,18607,18609,18611,18614],{"class":74,"line":122},[72,18576,18577],{"class":100},"trait",[72,18579,18580],{"class":440}," Db",[72,18582,616],{"class":446},[72,18584,2794],{"class":100},[72,18586,6686],{"class":89},[72,18588,476],{"class":446},[72,18590,676],{"class":100},[72,18592,8110],{"class":508},[72,18594,453],{"class":446},[72,18596,2454],{"class":508},[72,18598,512],{"class":100},[72,18600,932],{"class":440},[72,18602,682],{"class":446},[72,18604,578],{"class":100},[72,18606,540],{"class":440},[72,18608,543],{"class":446},[72,18610,546],{"class":440},[72,18612,18613],{"class":446},">;",[72,18615,1181],{"class":446},[72,18617,18618],{"class":74,"line":260},[72,18619,465],{"emptyLinePlaceholder":464},[72,18621,18622,18624,18627,18629,18631,18633,18636,18638,18640,18642,18644,18646,18648,18650,18652],{"class":74,"line":274},[72,18623,569],{"class":100},[72,18625,18626],{"class":89}," find_name",[72,18628,476],{"class":446},[72,18630,10464],{"class":508},[72,18632,512],{"class":100},[72,18634,18635],{"class":100}," &impl",[72,18637,18580],{"class":440},[72,18639,453],{"class":446},[72,18641,2454],{"class":508},[72,18643,512],{"class":100},[72,18645,932],{"class":440},[72,18647,682],{"class":446},[72,18649,578],{"class":100},[72,18651,515],{"class":440},[72,18653,503],{"class":446},[72,18655,18656,18659,18661,18663,18665,18667,18669,18671,18673,18675,18678,18680,18682],{"class":74,"line":287},[72,18657,18658],{"class":508},"    db",[72,18660,627],{"class":100},[72,18662,5012],{"class":89},[72,18664,476],{"class":446},[72,18666,2425],{"class":508},[72,18668,682],{"class":446},[72,18670,627],{"class":100},[72,18672,7244],{"class":89},[72,18674,476],{"class":446},[72,18676,18677],{"class":93},"\"匿名\"",[72,18679,627],{"class":100},[72,18681,630],{"class":89},[72,18683,2501],{"class":446},[72,18685,18686],{"class":74,"line":301},[72,18687,560],{"class":446},[72,18689,18690],{"class":74,"line":317},[72,18691,465],{"emptyLinePlaceholder":464},[72,18693,18694,18696,18698],{"class":74,"line":331},[72,18695,470],{"class":446},[72,18697,18009],{"class":473},[72,18699,2786],{"class":446},[72,18701,18702,18704,18707,18709],{"class":74,"line":353},[72,18703,569],{"class":100},[72,18705,18706],{"class":89}," test_find",[72,18708,575],{"class":446},[72,18710,503],{"class":446},[72,18712,18713,18715,18717,18720,18722,18725,18727,18729],{"class":74,"line":602},[72,18714,605],{"class":100},[72,18716,3781],{"class":100},[72,18718,18719],{"class":508}," mock",[72,18721,611],{"class":100},[72,18723,18724],{"class":440}," MockDb",[72,18726,443],{"class":100},[72,18728,3877],{"class":89},[72,18730,1500],{"class":446},[72,18732,18733,18736,18738,18741,18743,18745],{"class":74,"line":657},[72,18734,18735],{"class":508},"    mock",[72,18737,627],{"class":100},[72,18739,18740],{"class":89},"expect_get",[72,18742,575],{"class":446},[72,18744,1928],{"class":78},[72,18746,18747],{"class":82}," 预期 get 会被调用\n",[72,18749,18750,18752,18755,18757,18760,18762,18764,18766,18768,18770],{"class":74,"line":691},[72,18751,1776],{"class":100},[72,18753,18754],{"class":89},"with",[72,18756,476],{"class":446},[72,18758,18759],{"class":89},"eq",[72,18761,476],{"class":446},[72,18763,2062],{"class":89},[72,18765,2267],{"class":440},[72,18767,1804],{"class":446},[72,18769,919],{"class":78},[72,18771,18772],{"class":82}," 参数必须是 1\n",[72,18774,18775,18777,18780,18782,18784],{"class":74,"line":720},[72,18776,1776],{"class":100},[72,18778,18779],{"class":89},"times",[72,18781,476],{"class":446},[72,18783,2062],{"class":89},[72,18785,1771],{"class":446},[72,18787,18788,18790,18793,18795,18797,18799,18801,18803,18805,18807,18809,18811],{"class":74,"line":725},[72,18789,1776],{"class":100},[72,18791,18792],{"class":89},"returning",[72,18794,476],{"class":446},[72,18796,7401],{"class":100},[72,18798,3585],{"class":508},[72,18800,7401],{"class":100},[72,18802,1302],{"class":440},[72,18804,476],{"class":446},[72,18806,2494],{"class":93},[72,18808,627],{"class":100},[72,18810,630],{"class":89},[72,18812,18813],{"class":446},"()));\n",[72,18815,18816,18818,18820,18823,18825,18827,18830,18832,18834,18836,18838],{"class":74,"line":733},[72,18817,15223],{"class":89},[72,18819,476],{"class":446},[72,18821,18822],{"class":89},"find_name",[72,18824,476],{"class":446},[72,18826,676],{"class":100},[72,18828,18829],{"class":508},"mock",[72,18831,453],{"class":446},[72,18833,4273],{"class":89},[72,18835,2399],{"class":446},[72,18837,624],{"class":93},[72,18839,766],{"class":446},[72,18841,18842,18844],{"class":74,"line":769},[72,18843,596],{"class":78},[72,18845,18846],{"class":82}," 为什么 mock 而不是连真库？单元测试要快、稳定、可并行，\n",[72,18848,18849,18851],{"class":74,"line":774},[72,18850,596],{"class":78},[72,18852,18853],{"class":82}," 数据库交互的正确性交给少量集成测试去验证\n",[72,18855,18856],{"class":74,"line":782},[72,18857,560],{"class":446},[24,18859],{},[27,18861,18863],{"id":18862},"附录如何为项目选库","附录：如何为项目选库",[15,18865,18866],{},[19,18867,18868],{},"凭这三条原则做绝大多数决策：",[18870,18871,18872,18878,18884],"ol",{},[15587,18873,18874,18877],{},[19,18875,18876],{},"看生态位","：Rust 生态每个领域通常有一个「事实标准」（serde、tokio、reqwest、clap、sqlx……）。没有特殊理由就用它——文档最多、Stack Overflow 答案最全、其他库的集成最好。",[15587,18879,18880,18883],{},[19,18881,18882],{},"看维护状态","：在 crates.io 看最近更新时间和下载量；在 GitHub 看 issue 响应。两年没更新且 issue 堆积的库要警惕。",[15587,18885,18886,18889,18890,18893],{},[19,18887,18888],{},"看编译期 vs 运行时的取舍","：Rust 社区的偏好是",[19,18891,18892],{},"能在编译期抓到的错误绝不拖到运行时","（sqlx 的编译期 SQL 校验、axum 的类型化提取器、serde 的 derive）。选库时倾向这种风格的，长期少踩坑。",[15,18895,18896],{},[19,18897,18898],{},"本项目速查表：",[1566,18900,18901,18911],{},[1569,18902,18903],{},[1572,18904,18905,18908],{},[1575,18906,18907],{},"需求",[1575,18909,18910],{},"用这个",[1585,18912,18913,18921,18929,18936,18943,18951,18959,18967,18975,18983,18991,18999,19007,19015,19022],{},[1572,18914,18915,18918],{},[1590,18916,18917],{},"JSON \u002F 配置序列化",[1590,18919,18920],{},"serde + serde_json \u002F toml",[1572,18922,18923,18926],{},[1590,18924,18925],{},"应用错误处理",[1590,18927,18928],{},"anyhow（库用 thiserror）",[1572,18930,18931,18934],{},[1590,18932,18933],{},"异步 \u002F 网络服务",[1590,18935,2778],{},[1572,18937,18938,18941],{},[1590,18939,18940],{},"调 HTTP API",[1590,18942,5745],{},[1572,18944,18945,18948],{},[1590,18946,18947],{},"写 HTTP 服务",[1590,18949,18950],{},"axum",[1572,18952,18953,18956],{},[1590,18954,18955],{},"命令行工具",[1590,18957,18958],{},"clap",[1572,18960,18961,18964],{},[1590,18962,18963],{},"日志",[1590,18965,18966],{},"tracing（简单场景 log + env_logger）",[1572,18968,18969,18972],{},[1590,18970,18971],{},"日期时间",[1590,18973,18974],{},"chrono",[1572,18976,18977,18980],{},[1590,18978,18979],{},"随机数",[1590,18981,18982],{},"rand",[1572,18984,18985,18988],{},[1590,18986,18987],{},"正则",[1590,18989,18990],{},"regex",[1572,18992,18993,18996],{},[1590,18994,18995],{},"迭代器不够用",[1590,18997,18998],{},"itertools",[1572,19000,19001,19004],{},[1590,19002,19003],{},"CPU 密集并行",[1590,19005,19006],{},"rayon",[1572,19008,19009,19012],{},[1590,19010,19011],{},"数据库",[1590,19013,19014],{},"sqlx（要 ORM 再看 sea-orm）",[1572,19016,19017,19020],{},[1590,19018,19019],{},"环境变量",[1590,19021,16633],{},[1572,19023,19024,19027],{},[1590,19025,19026],{},"UUID",[1590,19028,19029],{},"uuid",[12,19031,19032],{},[15,19033,19034,19035,19038],{},"最后一条建议：",[19,19036,19037],{},"不要一次全用","。新项目从「std + serde + tokio + 领域必需的一两个库」开始，用到什么加什么。Rust 的编译器会接住你，依赖越少，构建越快，心智越轻。",[19040,19041,19042],"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":68,"searchDepth":122,"depth":122,"links":19044},[19045,19049,19057,19062,19070,19077,19084,19089,19094,19099,19100,19104,19105,19111,19116,19128,19134],{"id":29,"depth":86,"text":30,"children":19046},[19047,19048],{"id":60,"depth":107,"text":61},{"id":189,"depth":107,"text":190},{"id":381,"depth":86,"text":382,"children":19050},[19051,19052,19053,19054,19056],{"id":385,"depth":107,"text":386},{"id":406,"depth":107,"text":407},{"id":863,"depth":107,"text":864},{"id":1210,"depth":107,"text":19055},"1.4 处理不确定结构的 JSON：Value",{"id":1444,"depth":107,"text":1445},{"id":1545,"depth":86,"text":1546,"children":19058},[19059,19060,19061],{"id":1549,"depth":107,"text":1550},{"id":1623,"depth":107,"text":1624},{"id":2161,"depth":107,"text":2162},{"id":2711,"depth":86,"text":2712,"children":19063},[19064,19065,19066,19067,19068,19069],{"id":2715,"depth":107,"text":2716},{"id":2758,"depth":107,"text":2759},{"id":2946,"depth":107,"text":2947},{"id":3390,"depth":107,"text":3391},{"id":3620,"depth":107,"text":3621},{"id":4011,"depth":107,"text":4012},{"id":4808,"depth":86,"text":4809,"children":19071},[19072,19073,19074,19075,19076],{"id":4812,"depth":107,"text":4813},{"id":4840,"depth":107,"text":4841},{"id":5177,"depth":107,"text":5178},{"id":5684,"depth":107,"text":5685},{"id":5951,"depth":107,"text":5952},{"id":6251,"depth":86,"text":6252,"children":19078},[19079,19080,19081,19082,19083],{"id":6255,"depth":107,"text":6256},{"id":6305,"depth":107,"text":6306},{"id":6843,"depth":107,"text":6844},{"id":7942,"depth":107,"text":7943},{"id":8877,"depth":107,"text":8878},{"id":9035,"depth":86,"text":9036,"children":19085},[19086,19087,19088],{"id":9039,"depth":107,"text":9040},{"id":9078,"depth":107,"text":9079},{"id":9923,"depth":107,"text":9924},{"id":10077,"depth":86,"text":10078,"children":19090},[19091,19092,19093],{"id":10081,"depth":107,"text":10082},{"id":10108,"depth":107,"text":10109},{"id":10365,"depth":107,"text":10366},{"id":10859,"depth":86,"text":10860,"children":19095},[19096,19097,19098],{"id":10863,"depth":107,"text":10864},{"id":10887,"depth":107,"text":10888},{"id":11552,"depth":107,"text":11553},{"id":12055,"depth":86,"text":12056},{"id":12827,"depth":86,"text":12828,"children":19101},[19102,19103],{"id":12845,"depth":107,"text":12846},{"id":13672,"depth":107,"text":13673},{"id":13866,"depth":86,"text":13867},{"id":14893,"depth":86,"text":14894,"children":19106},[19107,19108,19110],{"id":14897,"depth":107,"text":14898},{"id":15466,"depth":107,"text":19109},"12.2 两个独立任务的并行：join",{"id":15581,"depth":107,"text":15582},{"id":15614,"depth":86,"text":15615,"children":19112},[19113,19114,19115],{"id":15618,"depth":107,"text":15619},{"id":15660,"depth":107,"text":15661},{"id":16539,"depth":107,"text":16540},{"id":16592,"depth":86,"text":16593,"children":19117},[19118,19119,19120,19121,19122,19123,19124,19125,19126,19127],{"id":16599,"depth":107,"text":16600},{"id":16656,"depth":107,"text":16657},{"id":16725,"depth":107,"text":16726},{"id":16832,"depth":107,"text":16833},{"id":16978,"depth":107,"text":16979},{"id":17150,"depth":107,"text":17151},{"id":17244,"depth":107,"text":17245},{"id":17497,"depth":107,"text":17498},{"id":17722,"depth":107,"text":17723},{"id":17847,"depth":107,"text":17848},{"id":17945,"depth":86,"text":17946,"children":19129},[19130,19131,19132,19133],{"id":17949,"depth":107,"text":17950},{"id":18349,"depth":107,"text":18350},{"id":18430,"depth":107,"text":18431},{"id":18510,"depth":107,"text":18511},{"id":18862,"depth":86,"text":18863},"2026-09-28","md",{},"\u002Fblog\u002Fstack\u002Frust-crate-in-action",{"title":6,"description":68},"blog\u002Fstack\u002Frust-crate-in-action",[],"PXv_4pVJ5lx9RSpZsK_lz77pLhhZ5wo7hSLtKcKwKSk",{"id":19144,"title":19145,"author":7,"body":19146,"category":7,"date":19847,"description":68,"extension":19136,"image":19848,"meta":19849,"navigation":464,"path":19850,"recommend":7,"seo":19851,"stem":19852,"tags":19853,"updated":7,"__hash__":19854},"stack\u002Fblog\u002Fstack\u002Fvscode-extends.md","VS Code 扩展插件",{"type":9,"value":19147,"toc":19835},[19148,19152,19178,19182,19216,19220,19342,19346,19472,19476,19542,19546,19595,19599,19641,19645,19751,19755,19821,19825],[27,19149,19151],{"id":19150},"rust-开发","🦀 Rust 开发",[15584,19153,19154,19162,19170],{},[15587,19155,19156,19161],{},[39,19157,19160],{"href":19158,"rel":19159},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=rust-lang.rust-analyzer",[43],"rust-analyzer"," - Rust 语言服务器，提供代码补全、跳转、重构和诊断",[15587,19163,19164,19169],{},[39,19165,19168],{"href":19166,"rel":19167},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vadimcn.vscode-lldb",[43],"CodeLLDB"," - 基于 LLDB 的调试支持",[15587,19171,19172,19177],{},[39,19173,19176],{"href":19174,"rel":19175},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=tamasfe.even-better-toml",[43],"Even Better TOML"," - TOML 语法高亮、校验和格式化",[27,19179,19181],{"id":19180},"python-开发","🐍 Python 开发",[15584,19183,19184,19192,19200,19208],{},[15587,19185,19186,19191],{},[39,19187,19190],{"href":19188,"rel":19189},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-python.python",[43],"Python"," - Python 运行、调试和项目管理",[15587,19193,19194,19199],{},[39,19195,19198],{"href":19196,"rel":19197},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-python.vscode-pylance",[43],"Pylance"," - Python 智能感知、类型检查和代码导航",[15587,19201,19202,19207],{},[39,19203,19206],{"href":19204,"rel":19205},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-python.debugpy",[43],"Python Debugger"," - Python 调试器",[15587,19209,19210,19215],{},[39,19211,19214],{"href":19212,"rel":19213},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-python.vscode-python-envs",[43],"Python Environments"," - Python 虚拟环境管理",[27,19217,19219],{"id":19218},"java-与-spring-开发","☕ Java 与 Spring 开发",[15584,19221,19222,19230,19238,19246,19254,19262,19270,19278,19286,19294,19302,19310,19318,19326,19334],{},[15587,19223,19224,19229],{},[39,19225,19228],{"href":19226,"rel":19227},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=pleiades.java-extension-pack-jdk",[43],"Extension Pack for Java Auto Config"," - Java 和 JDK 开发环境自动配置扩展包",[15587,19231,19232,19237],{},[39,19233,19236],{"href":19234,"rel":19235},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-java-pack",[43],"Extension Pack for Java"," - Java 开发扩展合集",[15587,19239,19240,19245],{},[39,19241,19244],{"href":19242,"rel":19243},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=redhat.java",[43],"Language Support for Java by Red Hat"," - Java 语言支持",[15587,19247,19248,19253],{},[39,19249,19252],{"href":19250,"rel":19251},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-java-debug",[43],"Debugger for Java"," - Java 调试支持",[15587,19255,19256,19261],{},[39,19257,19260],{"href":19258,"rel":19259},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-java-test",[43],"Test Runner for Java"," - Java 单元测试运行和调试",[15587,19263,19264,19269],{},[39,19265,19268],{"href":19266,"rel":19267},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-java-dependency",[43],"Project Manager for Java"," - Java 项目和依赖管理",[15587,19271,19272,19277],{},[39,19273,19276],{"href":19274,"rel":19275},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-maven",[43],"Maven for Java"," - Maven 项目支持",[15587,19279,19280,19285],{},[39,19281,19284],{"href":19282,"rel":19283},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-gradle",[43],"Gradle for Java"," - Gradle 项目支持",[15587,19287,19288,19293],{},[39,19289,19292],{"href":19290,"rel":19291},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vmware.vscode-boot-dev-pack",[43],"Spring Boot Extension Pack"," - Spring Boot 开发扩展合集",[15587,19295,19296,19301],{},[39,19297,19300],{"href":19298,"rel":19299},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vmware.vscode-spring-boot",[43],"Spring Boot Tools"," - Spring Boot 应用开发支持",[15587,19303,19304,19309],{},[39,19305,19308],{"href":19306,"rel":19307},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-spring-boot-dashboard",[43],"Spring Boot Dashboard"," - Spring Boot 应用管理面板",[15587,19311,19312,19317],{},[39,19313,19316],{"href":19314,"rel":19315},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.vscode-spring-initializr",[43],"Spring Initializr Java Support"," - 快速创建 Spring Boot 项目",[15587,19319,19320,19325],{},[39,19321,19324],{"href":19322,"rel":19323},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vscjava.migrate-java-to-azure",[43],"Java to Azure Migration"," - Java 项目迁移至 Azure 的辅助工具",[15587,19327,19328,19333],{},[39,19329,19332],{"href":19330,"rel":19331},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=redhat.vscode-xml",[43],"XML Language Support"," - XML 语法支持和校验",[15587,19335,19336,19341],{},[39,19337,19340],{"href":19338,"rel":19339},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=yangbaopan.vscode-java-ibatisx",[43],"iBatisX"," - iBatis\u002FMyBatis 开发辅助",[27,19343,19345],{"id":19344},"web-前端与-css","🌐 Web 前端与 CSS",[15584,19347,19348,19356,19364,19372,19380,19388,19396,19404,19412,19420,19428,19436,19444,19452,19460],{},[15587,19349,19350,19355],{},[39,19351,19354],{"href":19352,"rel":19353},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vue.volar",[43],"Vue - Official"," - Vue 官方语言工具",[15587,19357,19358,19363],{},[39,19359,19362],{"href":19360,"rel":19361},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=sdras.vue-vscode-snippets",[43],"Vue VSCode Snippets"," - Vue 常用代码片段",[15587,19365,19366,19371],{},[39,19367,19370],{"href":19368,"rel":19369},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ant-design-vue.vscode-ant-design-vue-helper",[43],"Ant Design Vue Helper"," - Ant Design Vue 组件辅助",[15587,19373,19374,19379],{},[39,19375,19378],{"href":19376,"rel":19377},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=tu6ge.naive-ui-intelligence",[43],"Naive UI Intelligence"," - Naive UI 组件智能提示",[15587,19381,19382,19387],{},[39,19383,19386],{"href":19384,"rel":19385},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bradlc.vscode-tailwindcss",[43],"Tailwind CSS IntelliSense"," - Tailwind CSS 智能提示",[15587,19389,19390,19395],{},[39,19391,19394],{"href":19392,"rel":19393},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=antfu.unocss",[43],"UnoCSS"," - UnoCSS 原子化 CSS 支持",[15587,19397,19398,19403],{},[39,19399,19402],{"href":19400,"rel":19401},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=csstools.postcss",[43],"PostCSS Language Support"," - PostCSS 语法支持",[15587,19405,19406,19411],{},[39,19407,19410],{"href":19408,"rel":19409},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=spook.easysass",[43],"Easy Sass"," - Sass 编译和开发支持",[15587,19413,19414,19419],{},[39,19415,19418],{"href":19416,"rel":19417},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=pranaygp.vscode-css-peek",[43],"CSS Peek"," - 从 HTML 快速查看 CSS 定义",[15587,19421,19422,19427],{},[39,19423,19426],{"href":19424,"rel":19425},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=formulahendry.auto-rename-tag",[43],"Auto Rename Tag"," - 自动重命名配对标签",[15587,19429,19430,19435],{},[39,19431,19434],{"href":19432,"rel":19433},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ritwickdey.liveserver",[43],"Live Server"," - 启动本地开发服务器并实时预览",[15587,19437,19438,19443],{},[39,19439,19442],{"href":19440,"rel":19441},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=jock.svg",[43],"SVG"," - SVG 编辑和预览支持",[15587,19445,19446,19451],{},[39,19447,19450],{"href":19448,"rel":19449},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=whtouche.vscode-js-console-utils",[43],"JavaScript Console Utils"," - 快速生成 JavaScript 调试日志",[15587,19453,19454,19459],{},[39,19455,19458],{"href":19456,"rel":19457},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=oxc.oxc-vscode",[43],"OXC"," - JavaScript 和 TypeScript 工具链支持",[15587,19461,19462,19467,19468,19471],{},[39,19463,19466],{"href":19464,"rel":19465},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=mikestead.dotenv",[43],"DotENV"," - ",[53,19469,19470],{},".env"," 文件语法高亮",[27,19473,19475],{"id":19474},"代码质量与格式化","🧹 代码质量与格式化",[15584,19477,19478,19486,19494,19502,19510,19518,19526,19534],{},[15587,19479,19480,19485],{},[39,19481,19484],{"href":19482,"rel":19483},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=dbaeumer.vscode-eslint",[43],"ESLint"," - JavaScript 和 TypeScript 代码检查",[15587,19487,19488,19493],{},[39,19489,19492],{"href":19490,"rel":19491},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=esbenp.prettier-vscode",[43],"Prettier - Code formatter"," - 多种语言代码格式化",[15587,19495,19496,19501],{},[39,19497,19500],{"href":19498,"rel":19499},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=editorconfig.editorconfig",[43],"EditorConfig for VS Code"," - 遵循项目 EditorConfig 配置",[15587,19503,19504,19509],{},[39,19505,19508],{"href":19506,"rel":19507},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=oderwat.indent-rainbow",[43],"Indent-Rainbow"," - 用颜色显示缩进层级",[15587,19511,19512,19517],{},[39,19513,19516],{"href":19514,"rel":19515},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=shardulm94.trailing-spaces",[43],"Trailing Spaces"," - 检查和清理行尾空格",[15587,19519,19520,19525],{},[39,19521,19524],{"href":19522,"rel":19523},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=streetsidesoftware.code-spell-checker",[43],"Code Spell Checker"," - 拼写检查",[15587,19527,19528,19533],{},[39,19529,19532],{"href":19530,"rel":19531},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=richie5um2.vscode-sort-json",[43],"Sort JSON objects"," - JSON 对象排序",[15587,19535,19536,19541],{},[39,19537,19540],{"href":19538,"rel":19539},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=mechatroner.rainbow-csv",[43],"Rainbow CSV"," - CSV 文件高亮和查询",[27,19543,19545],{"id":19544},"git版本控制与协作","🔀 Git、版本控制与协作",[15584,19547,19548,19556,19564,19572,19580,19588],{},[15587,19549,19550,19555],{},[39,19551,19554],{"href":19552,"rel":19553},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=eamodio.gitlens",[43],"GitLens"," - Git 历史、代码作者和提交信息增强",[15587,19557,19558,19563],{},[39,19559,19562],{"href":19560,"rel":19561},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=mhutchie.git-graph",[43],"Git Graph"," - 可视化 Git 分支和提交历史",[15587,19565,19566,19571],{},[39,19567,19570],{"href":19568,"rel":19569},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=github.vscode-github-actions",[43],"GitHub Actions"," - 查看和管理 GitHub Actions 工作流",[15587,19573,19574,19579],{},[39,19575,19578],{"href":19576,"rel":19577},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vivaxy.vscode-conventional-commits",[43],"Conventional Commits"," - 辅助创建规范化提交信息",[15587,19581,19582,19587],{},[39,19583,19586],{"href":19584,"rel":19585},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=johnstoncode.svn-scm",[43],"SVN"," - SVN 源代码管理",[15587,19589,19590,19594],{},[39,19591,19586],{"href":19592,"rel":19593},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=eliean.vscode-svn",[43]," - SVN 操作和资源管理",[27,19596,19598],{"id":19597},"️-远程容器与桌面应用","🖥️ 远程、容器与桌面应用",[15584,19600,19601,19609,19617,19625,19633],{},[15587,19602,19603,19608],{},[39,19604,19607],{"href":19605,"rel":19606},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-vscode-remote.remote-ssh",[43],"Remote - SSH"," - 通过 SSH 连接远程开发环境",[15587,19610,19611,19616],{},[39,19612,19615],{"href":19613,"rel":19614},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-vscode-remote.remote-ssh-edit",[43],"Remote - SSH: Editing Configuration Files"," - 编辑 Remote-SSH 配置文件",[15587,19618,19619,19624],{},[39,19620,19623],{"href":19621,"rel":19622},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-vscode.remote-explorer",[43],"Remote Explorer"," - 管理远程开发目标",[15587,19626,19627,19632],{},[39,19628,19631],{"href":19629,"rel":19630},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-azuretools.vscode-containers",[43],"Containers"," - 容器创建、管理和调试",[15587,19634,19635,19640],{},[39,19636,19639],{"href":19637,"rel":19638},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=tauri-apps.tauri-vscode",[43],"Tauri"," - Tauri 桌面应用开发支持",[27,19642,19644],{"id":19643},"markdown接口与数据工具","📝 Markdown、接口与数据工具",[15584,19646,19647,19655,19663,19671,19679,19687,19695,19703,19711,19719,19727,19735,19743],{},[15587,19648,19649,19654],{},[39,19650,19653],{"href":19651,"rel":19652},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=yzhang.markdown-all-in-one",[43],"Markdown All in One"," - Markdown 编辑、预览和快捷操作增强",[15587,19656,19657,19662],{},[39,19658,19661],{"href":19659,"rel":19660},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=shd101wyy.markdown-preview-enhanced",[43],"Markdown Preview Enhanced"," - 增强 Markdown 预览和导出",[15587,19664,19665,19670],{},[39,19666,19669],{"href":19667,"rel":19668},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bierner.markdown-preview-github-styles",[43],"Markdown Preview Github Styling"," - 使用 GitHub 风格预览 Markdown",[15587,19672,19673,19678],{},[39,19674,19677],{"href":19675,"rel":19676},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=davidanson.vscode-markdownlint",[43],"markdownlint"," - Markdown 规范检查",[15587,19680,19681,19686],{},[39,19682,19685],{"href":19683,"rel":19684},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=humao.rest-client",[43],"REST Client"," - 在编辑器中发送 HTTP 请求",[15587,19688,19689,19694],{},[39,19690,19693],{"href":19691,"rel":19692},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=mariusalchimavicius.json-to-ts",[43],"JSON to TypeScript"," - 从 JSON 生成 TypeScript 类型",[15587,19696,19697,19702],{},[39,19698,19701],{"href":19699,"rel":19700},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=lokalise.i18n-ally",[43],"i18n Ally"," - 国际化文案管理和提示",[15587,19704,19705,19710],{},[39,19706,19709],{"href":19707,"rel":19708},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=mushan.vscode-paste-image",[43],"Paste Image"," - 将剪贴板图片保存并插入 Markdown",[15587,19712,19713,19718],{},[39,19714,19717],{"href":19715,"rel":19716},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=hediet.vscode-drawio",[43],"Draw.io Integration"," - 在 VS Code 中编辑 Draw.io 图表",[15587,19720,19721,19726],{},[39,19722,19725],{"href":19723,"rel":19724},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=zhucy.project-tree",[43],"Project Tree"," - 展示项目目录树",[15587,19728,19729,19734],{},[39,19730,19733],{"href":19731,"rel":19732},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=w88975.code-translate",[43],"Code Translate"," - 选中文本翻译",[15587,19736,19737,19742],{},[39,19738,19741],{"href":19739,"rel":19740},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=vs-publisher-1448185.keyoti-changeallendoflinesequence",[43],"Change All End of Line Sequence"," - 批量转换文件换行符",[15587,19744,19745,19750],{},[39,19746,19749],{"href":19747,"rel":19748},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=zlxag.ant",[43],"Ant"," - Ant 构建工具支持",[27,19752,19754],{"id":19753},"主题与编辑体验","🎨 主题与编辑体验",[15584,19756,19757,19765,19773,19781,19789,19797,19805,19813],{},[15587,19758,19759,19764],{},[39,19760,19763],{"href":19761,"rel":19762},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=akamud.vscode-theme-onedark",[43],"Atom One Dark Theme"," - 经典 Atom 深色配色主题",[15587,19766,19767,19772],{},[39,19768,19771],{"href":19769,"rel":19770},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=zhuangtongfa.Material-theme",[43],"One Dark Pro"," - 增强版 One Dark 主题",[15587,19774,19775,19780],{},[39,19776,19779],{"href":19777,"rel":19778},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=pkief.material-icon-theme",[43],"Material Icon Theme"," - Material Design 风格文件图标",[15587,19782,19783,19788],{},[39,19784,19787],{"href":19785,"rel":19786},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=afzalsayed96.icones",[43],"vscode-icons"," - 文件和文件夹图标主题",[15587,19790,19791,19796],{},[39,19792,19795],{"href":19793,"rel":19794},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=antfu.iconify",[43],"Iconify IntelliSense"," - Iconify 图标搜索和提示",[15587,19798,19799,19804],{},[39,19800,19803],{"href":19801,"rel":19802},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=alefragnani.bookmarks",[43],"Bookmarks"," - 代码书签管理",[15587,19806,19807,19812],{},[39,19808,19811],{"href":19809,"rel":19810},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=kisstkondoros.vscode-gutter-preview",[43],"Gutter Preview"," - 在编辑器边栏预览图片",[15587,19814,19815,19820],{},[39,19816,19819],{"href":19817,"rel":19818},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=naumovs.color-highlight",[43],"Color Highlight"," - 预览代码中的颜色值",[27,19822,19824],{"id":19823},"ai-辅助","🤖 AI 辅助",[15584,19826,19827],{},[15587,19828,19829,19834],{},[39,19830,19833],{"href":19831,"rel":19832},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=johnny-zhao.oai-compatible-copilot",[43],"OAI Compatible Copilot"," - 兼容 OAI API 的 AI 编程辅助",{"title":68,"searchDepth":122,"depth":122,"links":19836},[19837,19838,19839,19840,19841,19842,19843,19844,19845,19846],{"id":19150,"depth":86,"text":19151},{"id":19180,"depth":86,"text":19181},{"id":19218,"depth":86,"text":19219},{"id":19344,"depth":86,"text":19345},{"id":19474,"depth":86,"text":19475},{"id":19544,"depth":86,"text":19545},{"id":19597,"depth":86,"text":19598},{"id":19643,"depth":86,"text":19644},{"id":19753,"depth":86,"text":19754},{"id":19823,"depth":86,"text":19824},"2026-08-24","https:\u002F\u002Fimg.iice.fun\u002Fblog\u002F2026\u002F07\u002F29\u002F786a91b9c5514b9e0ecd273d14efc5f9.webp",{},"\u002Fblog\u002Fstack\u002Fvscode-extends",{"title":19145,"description":68},"blog\u002Fstack\u002Fvscode-extends",[],"dwCFEH4w5sc3wpBdr0dz14nEk23BQpZlM2mQ2Dy6X6Y",{"id":19856,"title":19857,"author":7,"body":19858,"category":7,"date":20070,"description":19862,"extension":19136,"image":20095,"meta":20096,"navigation":464,"path":20097,"recommend":7,"seo":20098,"stem":20099,"tags":20100,"updated":7,"__hash__":20101},"stack\u002Fblog\u002Fstack\u002Ffrontend-stack.md","我的前端实践方案",{"type":9,"value":19859,"toc":20083},[19860,19863,19867,19871,19907,19911,19923,19927,19949,19953,20042,20046,20050,20060,20064],[15,19861,19862],{},"这里记录我个人在开发中使用的前端开发方案。",[27,19864,19866],{"id":19865},"_1-️-核心工具链","1. 🛠️ 核心工具链",[58,19868,19870],{"id":19869},"_11-node-版本管理","1.1 Node 版本管理",[15584,19872,19873,19897],{},[15587,19874,19875,19882,19883],{},[19,19876,19877],{},[39,19878,19881],{"href":19879,"rel":19880},"https:\u002F\u002Fvolta.sh\u002F",[43],"Volta"," - 跨平台 Node 版本管理器\n",[15584,19884,19885,19894],{},[15587,19886,19887,19888,19893],{},"通过「",[39,19889,19892],{"href":19890,"rel":19891},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FGlossary\u002FShim",[43],"垫片","」机制保证 GUI 应用也能找到 Node",[15587,19895,19896],{},"完美兼容 VS Code 插件等场景",[15587,19898,19899,19906],{},[19,19900,19901],{},[39,19902,19905],{"href":19903,"rel":19904},"https:\u002F\u002Fgithub.com\u002FSchniz\u002Ffnm",[43],"fnm"," - 曾使用的工具，在 Windows 上对 GUI 应用支持不佳，已弃用",[58,19908,19910],{"id":19909},"_12-包管理器","1.2 包管理器",[15584,19912,19913],{},[15587,19914,19915,19922],{},[19,19916,19917],{},[39,19918,19921],{"href":19919,"rel":19920},"https:\u002F\u002Fpnpm.io\u002F",[43],"pnpm"," - 快速、节省磁盘空间的包管理器",[58,19924,19926],{"id":19925},"_13-代码质量","1.3 代码质量",[15584,19928,19929,19939],{},[15587,19930,19931,19938],{},[19,19932,19933],{},[39,19934,19937],{"href":19935,"rel":19936},"https:\u002F\u002Foxc.rs",[43],"oxc"," - 高性能的 JavaScript\u002FTypeScript 代码质量工具集（Linter + Formatter）",[15587,19940,19941,19948],{},[19,19942,19943],{},[39,19944,19947],{"href":19945,"rel":19946},"https:\u002F\u002Fgithub.com\u002Ftoplenboren\u002Fsimple-git-hooks",[43],"simple-git-hooks"," - 轻量级 Git Hooks 管理工具，在提交前进行代码检查",[27,19950,19952],{"id":19951},"_2-前端技术栈","2. 🚀 前端技术栈",[15584,19954,19955,19966,19977,19988,19999,20010,20021,20032],{},[15587,19956,19957,19958,19965],{},"构建工具：",[19,19959,19960],{},[39,19961,19964],{"href":19962,"rel":19963},"https:\u002F\u002Fvitejs.dev\u002F",[43],"Vite"," - 下一代前端构建工具",[15587,19967,19968,19969,19976],{},"JavaScript 框架：",[19,19970,19971],{},[39,19972,19975],{"href":19973,"rel":19974},"https:\u002F\u002Fvuejs.org\u002F",[43],"Vue 3"," - 渐进式 JavaScript 框架",[15587,19978,19979,19980,19987],{},"全栈框架：",[19,19981,19982],{},[39,19983,19986],{"href":19984,"rel":19985},"https:\u002F\u002Fnuxt.com\u002F",[43],"Nuxt 4"," - Vue.js 全栈框架",[15587,19989,19990,19991,19998],{},"状态管理：",[19,19992,19993],{},[39,19994,19997],{"href":19995,"rel":19996},"https:\u002F\u002Fpinia.vuejs.org\u002F",[43],"Pinia"," - Vue 3 官方推荐的状态管理库，提供类型安全的 Store",[15587,20000,20001,20002,20009],{},"UI 框架：",[19,20003,20004],{},[39,20005,20008],{"href":20006,"rel":20007},"https:\u002F\u002Fnuxtui.org\u002F",[43],"Nuxt UI"," - 组件丰富，提供官方 AI MCP 支持",[15587,20011,20012,20013,20020],{},"样式方案：",[19,20014,20015],{},[39,20016,20019],{"href":20017,"rel":20018},"https:\u002F\u002Ftailwindcss.com\u002F",[43],"Tailwind CSS"," - 原子化 CSS 框架",[15587,20022,20023,20024,20031],{},"工具函数库：",[19,20025,20026],{},[39,20027,20030],{"href":20028,"rel":20029},"https:\u002F\u002Fvueuse.org\u002F",[43],"VueUse"," - 基于组合式 API 的实用工具集合，包含大量常用组合式函数",[15587,20033,20034,20035,20041],{},"跨平台开发：",[19,20036,20037],{},[39,20038,19639],{"href":20039,"rel":20040},"https:\u002F\u002Ftauri.app\u002F",[43]," - 使用 Web 技术构建桌面应用",[27,20043,20045],{"id":20044},"_3-开发环境","3. 💻 开发环境",[58,20047,20049],{"id":20048},"_31-编辑器","3.1 编辑器",[15,20051,20052,20059],{},[19,20053,20054],{},[39,20055,20058],{"href":20056,"rel":20057},"https:\u002F\u002Fcode.visualstudio.com\u002F",[43],"Visual Studio Code"," - 强大的现代代码编辑器",[27,20061,20063],{"id":20062},"_4-更新日志","4. 📌 更新日志",[15584,20065,20066,20072],{},[15587,20067,20068,20071],{},[19,20069,20070],{},"2026-01-24"," - 初始版本，记录当前使用的前端技术栈",[15587,20073,20074,20077,20078],{},[19,20075,20076],{},"2026-08-21"," - 移除了vscode拓展，详见",[39,20079,20082],{"href":20080,"rel":20081},"https:\u002F\u002Fwww.iice.fun\u002Fmore\u002Fvscode-extends",[43],"VS Code 拓展合集",{"title":68,"searchDepth":122,"depth":122,"links":20084},[20085,20090,20091,20094],{"id":19865,"depth":86,"text":19866,"children":20086},[20087,20088,20089],{"id":19869,"depth":107,"text":19870},{"id":19909,"depth":107,"text":19910},{"id":19925,"depth":107,"text":19926},{"id":19951,"depth":86,"text":19952},{"id":20044,"depth":86,"text":20045,"children":20092},[20093],{"id":20048,"depth":107,"text":20049},{"id":20062,"depth":86,"text":20063},"https:\u002F\u002Fimg.iice.fun\u002Fblog\u002F2026\u002F01\u002F24\u002Fd403d2368ff08e1f95ef2b45b33c42fe.webp",{},"\u002Fblog\u002Fstack\u002Ffrontend-stack",{"title":19857,"description":19862},"blog\u002Fstack\u002Ffrontend-stack",[],"LnNQVjkeSQd0urbazbDJ6P8rYEtz4E3BEOCUz0ErR8Y",1792899441687]