[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ferror":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ferror":1378,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ferror":1404},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":14,"pinned":15,"draft":15,"body":16,"seo":1374,"stem":1375,"id":1376,"extension":1377},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ferror","第五章：错误处理","Rust 将错误明确分为两类：不可恢复的错误与可恢复的错误。通过 panic! 处理前一种，Result 处理后一种，这让程序的错误路径不再是隐式的控制流，而是强类型、必须处理的代码分支。配合 Option 对缺失值的处理以及丰富的组…","技能小册",[12,13],"rust","error","2023-11-03",false,{"type":17,"value":18,"toc":1337},"minimark",[19,23,40,47,63,70,76,104,111,118,121,148,155,160,171,210,235,242,249,266,282,288,294,297,321,334,349,352,360,401,422,437,444,471,515,529,532,540,608,628,631,638,647,652,740,748,781,789,836,849,856,865,868,913,928,949,955,997,1006,1023,1042,1045,1049,1060,1063,1079,1086,1100,1138,1163,1173,1202,1212,1215,1232,1237,1252,1262,1274,1277,1333],[20,21,8],"h1",{"id":22},"第五章错误处理",[24,25,26,27,31,32,35,36,39],"p",{},"Rust 将错误明确分为两类：不可恢复的错误与可恢复的错误。通过 ",[28,29,30],"code",{},"panic!"," 处理前一种，",[28,33,34],{},"Result"," 处理后一种，这让程序的错误路径不再是隐式的控制流，而是强类型、必须处理的代码分支。配合 ",[28,37,38],{},"Option"," 对缺失值的处理以及丰富的组合子与生态库，Rust 形成了安全、清晰的错误处理哲学。",[41,42,44,45],"h2",{"id":43},"_51-不可恢复错误与-panic","5.1 不可恢复错误与 ",[28,46,30],{},[24,48,49,50,54,55,58,59,62],{},"当程序遇到无法继续运行的严重问题时，会引发 ",[51,52,53],"strong",{},"panic","。默认行为是",[51,56,57],{},"展开","线程，清理栈帧并释放资源。对于致命错误，也可以直接 ",[51,60,61],{},"abort","。",[64,65,67,69],"h3",{"id":66},"panic-的使用",[28,68,30],{}," 的使用",[24,71,72,73,75],{},"调用 ",[28,74,30],{}," 宏会立即中止当前线程：",[77,78,82],"pre",{"className":79,"code":80,"language":12,"meta":81,"style":81},"language-rust shiki shiki-themes material-theme-lighter github-light github-dark","fn main() {\n    panic!(\"crash and burn\");\n}\n","",[28,83,84,92,98],{"__ignoreMap":81},[85,86,89],"span",{"class":87,"line":88},"line",1,[85,90,91],{},"fn main() {\n",[85,93,95],{"class":87,"line":94},2,[85,96,97],{},"    panic!(\"crash and burn\");\n",[85,99,101],{"class":87,"line":100},3,[85,102,103],{},"}\n",[24,105,106,107,110],{},"程序将打印错误信息并退出。如果是在多线程环境中，只中止发生 panic 的线程，其它线程仍可继续（但通常会让整个进程受影响）。可以通过 ",[28,108,109],{},"std::panic::set_hook"," 自定义 panic 行为。",[64,112,114,115],{"id":113},"调试栈信息rust_backtrace","调试栈信息：",[28,116,117],{},"RUST_BACKTRACE",[24,119,120],{},"要查看 panic 时的调用栈，设置环境变量：",[77,122,126],{"className":123,"code":124,"language":125,"meta":81,"style":81},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","RUST_BACKTRACE=1 cargo run\n","bash",[28,127,128],{"__ignoreMap":81},[85,129,130,133,137,141,145],{"class":87,"line":88},[85,131,117],{"class":132},"su5hD",[85,134,136],{"class":135},"smGrS","=",[85,138,140],{"class":139},"s_sjI","1",[85,142,144],{"class":143},"sbgvK"," cargo",[85,146,147],{"class":139}," run\n",[24,149,150,151,154],{},"这会打印详尽的回溯信息，辅助定位问题。设为 ",[28,152,153],{},"full"," 则显示所有栈帧。在发行版中，由于优化可能影响栈帧完整性，建议使用 debug 版本调试。",[64,156,158],{"id":157},"catch_unwind",[28,159,157],{},[24,161,162,163,166,167,170],{},"Rust 提供了 ",[28,164,165],{},"std::panic::catch_unwind"," 来捕获 panic，使线程可以在 panic 后继续执行。但它",[51,168,169],{},"不应被用作常规错误处理","，主要用于 FFI 边界或隔离崩溃（例如测试框架、web 服务器的工作线程）。",[77,172,174],{"className":79,"code":173,"language":12,"meta":81,"style":81},"use std::panic;\n\nlet result = panic::catch_unwind(|| {\n    panic!(\"oops!\");\n});\nassert!(result.is_err());\n",[28,175,176,181,187,192,198,204],{"__ignoreMap":81},[85,177,178],{"class":87,"line":88},[85,179,180],{},"use std::panic;\n",[85,182,183],{"class":87,"line":94},[85,184,186],{"emptyLinePlaceholder":185},true,"\n",[85,188,189],{"class":87,"line":100},[85,190,191],{},"let result = panic::catch_unwind(|| {\n",[85,193,195],{"class":87,"line":194},4,[85,196,197],{},"    panic!(\"oops!\");\n",[85,199,201],{"class":87,"line":200},5,[85,202,203],{},"});\n",[85,205,207],{"class":87,"line":206},6,[85,208,209],{},"assert!(result.is_err());\n",[24,211,212,213,215,216,219,220,223,224,227,228,231,232,234],{},"被捕获的 panic 被视为 ",[28,214,34],{}," 的 ",[28,217,218],{},"Err"," 变体，内部是 ",[28,221,222],{},"Box\u003Cdyn Any + Send>","，可以尝试 ",[28,225,226],{},"downcast_ref"," 提取原消息。注意：开启 ",[28,229,230],{},"panic = 'abort'"," 时，",[28,233,157],{}," 无效。",[64,236,238,239,241],{"id":237},"与-abort-的区别","与 ",[28,240,61],{}," 的区别",[24,243,244,245,248],{},"可以在 ",[28,246,247],{},"Cargo.toml"," 中配置 panic 策略：",[77,250,254],{"className":251,"code":252,"language":253,"meta":81,"style":81},"language-toml shiki shiki-themes material-theme-lighter github-light github-dark","[profile.release]\npanic = 'abort'\n","toml",[28,255,256,261],{"__ignoreMap":81},[85,257,258],{"class":87,"line":88},[85,259,260],{},"[profile.release]\n",[85,262,263],{"class":87,"line":94},[85,264,265],{},"panic = 'abort'\n",[267,268,269,276],"ul",{},[270,271,272,275],"li",{},[51,273,274],{},"展开（unwind）","：默认，释放资源，但增加二进制体积。",[270,277,278,281],{},[51,279,280],{},"终止（abort）","：直接退出程序，不调用析构函数，体积更小，适合嵌入式等场景。",[41,283,285,286],{"id":284},"_52-可恢复错误与-result","5.2 可恢复错误与 ",[28,287,34],{},[24,289,290,293],{},[28,291,292],{},"Result\u003CT, E>"," 是 Rust 处理可能失败的操作的标准方式，强迫调用者显式处理成功和失败两种情况。",[64,295,296],{"id":296},"定义",[77,298,300],{"className":79,"code":299,"language":12,"meta":81,"style":81},"enum Result\u003CT, E> {\n    Ok(T),\n    Err(E),\n}\n",[28,301,302,307,312,317],{"__ignoreMap":81},[85,303,304],{"class":87,"line":88},[85,305,306],{},"enum Result\u003CT, E> {\n",[85,308,309],{"class":87,"line":94},[85,310,311],{},"    Ok(T),\n",[85,313,314],{"class":87,"line":100},[85,315,316],{},"    Err(E),\n",[85,318,319],{"class":87,"line":194},[85,320,103],{},[24,322,323,326,327,330,331,333],{},[28,324,325],{},"T"," 是成功时的返回值，",[28,328,329],{},"E"," 是错误类型。标准库中许多操作都返回 ",[28,332,34],{},"，例如文件操作：",[77,335,337],{"className":79,"code":336,"language":12,"meta":81,"style":81},"use std::fs::File;\nlet f = File::open(\"hello.txt\");\n",[28,338,339,344],{"__ignoreMap":81},[85,340,341],{"class":87,"line":88},[85,342,343],{},"use std::fs::File;\n",[85,345,346],{"class":87,"line":94},[85,347,348],{},"let f = File::open(\"hello.txt\");\n",[64,350,351],{"id":351},"处理方式",[24,353,354,359],{},[51,355,356],{},[28,357,358],{},"match"," 是最基本、最显式的处理方法：",[77,361,363],{"className":79,"code":362,"language":12,"meta":81,"style":81},"let f = match f {\n    Ok(file) => file,\n    Err(error) => {\n        eprintln!(\"Problem opening the file: {:?}\", error);\n        return;\n    }\n};\n",[28,364,365,370,375,380,385,390,395],{"__ignoreMap":81},[85,366,367],{"class":87,"line":88},[85,368,369],{},"let f = match f {\n",[85,371,372],{"class":87,"line":94},[85,373,374],{},"    Ok(file) => file,\n",[85,376,377],{"class":87,"line":100},[85,378,379],{},"    Err(error) => {\n",[85,381,382],{"class":87,"line":194},[85,383,384],{},"        eprintln!(\"Problem opening the file: {:?}\", error);\n",[85,386,387],{"class":87,"line":200},[85,388,389],{},"        return;\n",[85,391,392],{"class":87,"line":206},[85,393,394],{},"    }\n",[85,396,398],{"class":87,"line":397},7,[85,399,400],{},"};\n",[24,402,403,404,409,410,415,416,418,419,421],{},"原型开发或确信不会出错时，可以使用 ",[51,405,406],{},[28,407,408],{},"unwrap"," 和 ",[51,411,412],{},[28,413,414],{},"expect","。它们在 ",[28,417,218],{}," 时直接 panic，",[28,420,414],{}," 允许附加提示消息：",[77,423,425],{"className":79,"code":424,"language":12,"meta":81,"style":81},"let f = File::open(\"hello.txt\").unwrap();\nlet f = File::open(\"hello.txt\").expect(\"Failed to open hello.txt\");\n",[28,426,427,432],{"__ignoreMap":81},[85,428,429],{"class":87,"line":88},[85,430,431],{},"let f = File::open(\"hello.txt\").unwrap();\n",[85,433,434],{"class":87,"line":94},[85,435,436],{},"let f = File::open(\"hello.txt\").expect(\"Failed to open hello.txt\");\n",[64,438,440,443],{"id":439},"运算符",[28,441,442],{},"?"," 运算符",[24,445,446,448,449,452,453,456,457,460,461,464,465,467,468,470],{},[28,447,442],{}," 是传播错误的便捷语法：如果是 ",[28,450,451],{},"Ok(v)","，则提取 ",[28,454,455],{},"v","；如果是 ",[28,458,459],{},"Err(e)","，则立即从当前函数返回 ",[28,462,463],{},"Err(From::from(e))","（自动进行错误类型转换）。它只能在返回 ",[28,466,34],{},"（或 ",[28,469,38],{},"）的函数中使用。",[77,472,474],{"className":79,"code":473,"language":12,"meta":81,"style":81},"use std::fs;\nuse std::io;\n\nfn read_username() -> Result\u003CString, io::Error> {\n    let mut username = String::new();\n    fs::File::open(\"hello.txt\")?.read_to_string(&mut username)?;\n    Ok(username)\n}\n",[28,475,476,481,486,490,495,500,505,510],{"__ignoreMap":81},[85,477,478],{"class":87,"line":88},[85,479,480],{},"use std::fs;\n",[85,482,483],{"class":87,"line":94},[85,484,485],{},"use std::io;\n",[85,487,488],{"class":87,"line":100},[85,489,186],{"emptyLinePlaceholder":185},[85,491,492],{"class":87,"line":194},[85,493,494],{},"fn read_username() -> Result\u003CString, io::Error> {\n",[85,496,497],{"class":87,"line":200},[85,498,499],{},"    let mut username = String::new();\n",[85,501,502],{"class":87,"line":206},[85,503,504],{},"    fs::File::open(\"hello.txt\")?.read_to_string(&mut username)?;\n",[85,506,507],{"class":87,"line":397},[85,508,509],{},"    Ok(username)\n",[85,511,513],{"class":87,"line":512},8,[85,514,103],{},[24,516,517,518,520,521,524,525,528],{},"当错误类型不同时，",[28,519,442],{}," 通过 ",[28,522,523],{},"From"," trait 将底层错误转换为函数的返回错误类型。这要求返回的错误类型实现了 ",[28,526,527],{},"From\u003C实际错误类型>","，通常由库提供。",[64,530,531],{"id":531},"组合子",[24,533,534,536,537,539],{},[28,535,34],{}," 提供了一系列组合子方法，可以链式处理，避免显式 ",[28,538,358],{},"：",[267,541,542,559,567,579,588,600],{},[270,543,544,547,548,551,552,555,556,558],{},[28,545,546],{},"map","：将 ",[28,549,550],{},"Ok(T)"," 映射为 ",[28,553,554],{},"Ok(U)","，对 ",[28,557,218],{}," 无影响。",[270,560,561,564,565,62],{},[28,562,563],{},"map_err","：转换 ",[28,566,218],{},[270,568,569,572,573,576,577,62],{},[28,570,571],{},"and_then","（flat_map）：当 ",[28,574,575],{},"Ok"," 时，调用闭包并返回可能失败的 ",[28,578,34],{},[270,580,581,584,585,587],{},[28,582,583],{},"or_else","：当 ",[28,586,218],{}," 时，调用闭包处理并可能恢复。",[270,589,590,593,594,596,597,599],{},[28,591,592],{},"unwrap_or","：提取 ",[28,595,575],{}," 值，若 ",[28,598,218],{}," 则返回默认值。",[270,601,602,539,605,607],{},[28,603,604],{},"unwrap_or_else",[28,606,218],{}," 时执行闭包产生默认值。",[77,609,611],{"className":79,"code":610,"language":12,"meta":81,"style":81},"let n = \"42\".parse::\u003Ci32>()\n    .map(|x| x * 2)\n    .unwrap_or(0);\n",[28,612,613,618,623],{"__ignoreMap":81},[85,614,615],{"class":87,"line":88},[85,616,617],{},"let n = \"42\".parse::\u003Ci32>()\n",[85,619,620],{"class":87,"line":94},[85,621,622],{},"    .map(|x| x * 2)\n",[85,624,625],{"class":87,"line":100},[85,626,627],{},"    .unwrap_or(0);\n",[24,629,630],{},"这些组合子让错误处理流更具表达力。",[64,632,634,635],{"id":633},"自定义错误类型与-thiserror","自定义错误类型与 ",[28,636,637],{},"thiserror",[24,639,640,641,409,644,62],{},"对于库代码，应该定义自己的错误类型，通常用枚举表示不同错误种类，并实现 ",[28,642,643],{},"std::fmt::Display",[28,645,646],{},"std::error::Error",[24,648,649],{},[51,650,651],{},"手动实现：",[77,653,655],{"className":79,"code":654,"language":12,"meta":81,"style":81},"#[derive(Debug)]\nenum MyError {\n    Io(std::io::Error),\n    Parse(std::num::ParseIntError),\n}\n\nimpl std::fmt::Display for MyError {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            MyError::Io(e) => write!(f, \"IO error: {e}\"),\n            MyError::Parse(e) => write!(f, \"Parse error: {e}\"),\n        }\n    }\n}\n\nimpl std::error::Error for MyError {}\n",[28,656,657,662,667,672,677,681,685,690,695,701,707,713,719,724,729,734],{"__ignoreMap":81},[85,658,659],{"class":87,"line":88},[85,660,661],{},"#[derive(Debug)]\n",[85,663,664],{"class":87,"line":94},[85,665,666],{},"enum MyError {\n",[85,668,669],{"class":87,"line":100},[85,670,671],{},"    Io(std::io::Error),\n",[85,673,674],{"class":87,"line":194},[85,675,676],{},"    Parse(std::num::ParseIntError),\n",[85,678,679],{"class":87,"line":200},[85,680,103],{},[85,682,683],{"class":87,"line":206},[85,684,186],{"emptyLinePlaceholder":185},[85,686,687],{"class":87,"line":397},[85,688,689],{},"impl std::fmt::Display for MyError {\n",[85,691,692],{"class":87,"line":512},[85,693,694],{},"    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n",[85,696,698],{"class":87,"line":697},9,[85,699,700],{},"        match self {\n",[85,702,704],{"class":87,"line":703},10,[85,705,706],{},"            MyError::Io(e) => write!(f, \"IO error: {e}\"),\n",[85,708,710],{"class":87,"line":709},11,[85,711,712],{},"            MyError::Parse(e) => write!(f, \"Parse error: {e}\"),\n",[85,714,716],{"class":87,"line":715},12,[85,717,718],{},"        }\n",[85,720,722],{"class":87,"line":721},13,[85,723,394],{},[85,725,727],{"class":87,"line":726},14,[85,728,103],{},[85,730,732],{"class":87,"line":731},15,[85,733,186],{"emptyLinePlaceholder":185},[85,735,737],{"class":87,"line":736},16,[85,738,739],{},"impl std::error::Error for MyError {}\n",[24,741,742,743,745,746,539],{},"然后为 ",[28,744,523],{}," 实现自动转换，以启用 ",[28,747,442],{},[77,749,751],{"className":79,"code":750,"language":12,"meta":81,"style":81},"impl From\u003Cstd::io::Error> for MyError {\n    fn from(e: std::io::Error) -> Self { MyError::Io(e) }\n}\nimpl From\u003Cstd::num::ParseIntError> for MyError {\n    fn from(e: std::num::ParseIntError) -> Self { MyError::Parse(e) }\n}\n",[28,752,753,758,763,767,772,777],{"__ignoreMap":81},[85,754,755],{"class":87,"line":88},[85,756,757],{},"impl From\u003Cstd::io::Error> for MyError {\n",[85,759,760],{"class":87,"line":94},[85,761,762],{},"    fn from(e: std::io::Error) -> Self { MyError::Io(e) }\n",[85,764,765],{"class":87,"line":100},[85,766,103],{},[85,768,769],{"class":87,"line":194},[85,770,771],{},"impl From\u003Cstd::num::ParseIntError> for MyError {\n",[85,773,774],{"class":87,"line":200},[85,775,776],{},"    fn from(e: std::num::ParseIntError) -> Self { MyError::Parse(e) }\n",[85,778,779],{"class":87,"line":206},[85,780,103],{},[24,782,783,788],{},[51,784,785,787],{},[28,786,637],{}," crate"," 则可以大幅简化这个过程：",[77,790,792],{"className":79,"code":791,"language":12,"meta":81,"style":81},"use thiserror::Error;\n\n#[derive(Error, Debug)]\nenum MyError {\n    #[error(\"IO error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"Parse error: {0}\")]\n    Parse(#[from] std::num::ParseIntError),\n}\n",[28,793,794,799,803,808,812,817,822,827,832],{"__ignoreMap":81},[85,795,796],{"class":87,"line":88},[85,797,798],{},"use thiserror::Error;\n",[85,800,801],{"class":87,"line":94},[85,802,186],{"emptyLinePlaceholder":185},[85,804,805],{"class":87,"line":100},[85,806,807],{},"#[derive(Error, Debug)]\n",[85,809,810],{"class":87,"line":194},[85,811,666],{},[85,813,814],{"class":87,"line":200},[85,815,816],{},"    #[error(\"IO error: {0}\")]\n",[85,818,819],{"class":87,"line":206},[85,820,821],{},"    Io(#[from] std::io::Error),\n",[85,823,824],{"class":87,"line":397},[85,825,826],{},"    #[error(\"Parse error: {0}\")]\n",[85,828,829],{"class":87,"line":512},[85,830,831],{},"    Parse(#[from] std::num::ParseIntError),\n",[85,833,834],{"class":87,"line":697},[85,835,103],{},[24,837,838,839,842,843,409,846,848],{},"该宏会自动生成 ",[28,840,841],{},"Display","、",[28,844,845],{},"Error",[28,847,523],{}," 的实现，是库开发的首选。",[41,850,852,853,855],{"id":851},"_53-option-与缺失值处理","5.3 ",[28,854,38],{}," 与缺失值处理",[24,857,858,861,862,864],{},[28,859,860],{},"Option\u003CT>"," 表达“可能存在值”的概念，是空值安全的替代品。它也拥有丰富的组合子和与 ",[28,863,34],{}," 交互的能力。",[64,866,867],{"id":867},"常用方法",[267,869,870,887,899],{},[270,871,872,875,876,593,879,882,883,886],{},[28,873,874],{},"unwrap()"," \u002F ",[28,877,878],{},"expect()",[28,880,881],{},"Some"," 中的值，若是 ",[28,884,885],{},"None"," 则 panic。",[270,888,889,842,891,842,893,896,897,62],{},[28,890,546],{},[28,892,571],{},[28,894,895],{},"filter","：安全地转换 ",[28,898,38],{},[270,900,901,875,904,547,907,909,910,912],{},[28,902,903],{},"ok_or",[28,905,906],{},"ok_or_else",[28,908,860],{}," 转为 ",[28,911,292],{},"，提供错误信息。",[77,914,916],{"className":79,"code":915,"language":12,"meta":81,"style":81},"let config = Some(\"value\");\nlet value = config.ok_or(\"missing config\")?; \u002F\u002F 返回 Result\n",[28,917,918,923],{"__ignoreMap":81},[85,919,920],{"class":87,"line":88},[85,921,922],{},"let config = Some(\"value\");\n",[85,924,925],{"class":87,"line":94},[85,926,927],{},"let value = config.ok_or(\"missing config\")?; \u002F\u002F 返回 Result\n",[267,929,930,941],{},[270,931,932,842,935,937,938,940],{},[28,933,934],{},"or",[28,936,583],{},"：当为 ",[28,939,885],{}," 时提供回退值或执行闭包。",[270,942,943,946,947,62],{},[28,944,945],{},"take","：获取所有权并留下 ",[28,948,885],{},[64,950,238,952,954],{"id":951},"与-result-互相转换",[28,953,34],{}," 互相转换",[267,956,957,971],{},[270,958,959,961,962,539,964,967,968,62],{},[28,960,38],{}," → ",[28,963,34],{},[28,965,966],{},"opt.ok_or(error)"," 或 ",[28,969,970],{},"opt.ok_or_else(|| error)",[270,972,973,961,975,539,977,980,981,983,984,987,988,983,990,992,993,996],{},[28,974,34],{},[28,976,38],{},[28,978,979],{},"result.ok()"," 将 ",[28,982,451],{}," 变为 ",[28,985,986],{},"Some(v)","，",[28,989,218],{},[28,991,885],{},"；",[28,994,995],{},"result.err()"," 获取错误。",[64,998,1000,1001,1003,1004],{"id":999},"使用-处理-option","使用 ",[28,1002,442],{}," 处理 ",[28,1005,38],{},[24,1007,1008,1009,1011,1012,1014,1015,1017,1018,1020,1021,62],{},"当函数返回类型是 ",[28,1010,860],{}," 时，也可以在 ",[28,1013,38],{}," 值上使用 ",[28,1016,442],{},"。如果为 ",[28,1019,885],{},"，函数会提前返回 ",[28,1022,885],{},[77,1024,1026],{"className":79,"code":1025,"language":12,"meta":81,"style":81},"fn last_char_of_first_line(text: &str) -> Option\u003Cchar> {\n    text.lines().next()?.chars().last()\n}\n",[28,1027,1028,1033,1038],{"__ignoreMap":81},[85,1029,1030],{"class":87,"line":88},[85,1031,1032],{},"fn last_char_of_first_line(text: &str) -> Option\u003Cchar> {\n",[85,1034,1035],{"class":87,"line":94},[85,1036,1037],{},"    text.lines().next()?.chars().last()\n",[85,1039,1040],{"class":87,"line":100},[85,1041,103],{},[24,1043,1044],{},"这极大地简化了可选值链式处理。",[41,1046,1048],{"id":1047},"_54-错误传播最佳实践","5.4 错误传播最佳实践",[24,1050,1051,1052,1055,1056,1059],{},"Rust 错误处理的实践根据场景分化：",[51,1053,1054],{},"库","应提供明确、可匹配的错误类型；",[51,1057,1058],{},"应用程序","则更侧重于方便的报告和上下文。",[64,1061,1062],{"id":1062},"库的错误类型",[24,1064,1065,1066,1068,1069,1072,1073,1075,1076,1078],{},"库应定义清晰区分错误种类的枚举（通常借助 ",[28,1067,637],{},"），让调用者能精确匹配错误原因并决定处理策略。不要直接将上游的 ",[28,1070,1071],{},"io::Error"," 或其他 extern 错误暴露给用户，而是包装到自己的错误变体中。同时实现相应的 ",[28,1074,523],{},"，使得 ",[28,1077,442],{}," 可以无缝转换。",[64,1080,1082,1083],{"id":1081},"应用程序的错误处理anyhow","应用程序的错误处理：",[28,1084,1085],{},"anyhow",[24,1087,1088,1089,1091,1092,1095,1096,1099],{},"对于二进制程序，通常不需要匹配具体错误种类，而是需要将所有错误向上传播并最终报告给用户并退出。",[28,1090,1085],{}," crate 为此提供了便利的 ",[28,1093,1094],{},"anyhow::Error"," 类型，它类似于 ",[28,1097,1098],{},"Box\u003Cdyn Error>","，但附带额外的上下文追踪。",[77,1101,1103],{"className":79,"code":1102,"language":12,"meta":81,"style":81},"use anyhow::{Context, Result};\n\nfn read_config(path: &str) -> Result\u003CString> {\n    let content = std::fs::read_to_string(path)\n        .with_context(|| format!(\"Failed to read config from {path}\"))?;\n    Ok(content)\n}\n",[28,1104,1105,1110,1114,1119,1124,1129,1134],{"__ignoreMap":81},[85,1106,1107],{"class":87,"line":88},[85,1108,1109],{},"use anyhow::{Context, Result};\n",[85,1111,1112],{"class":87,"line":94},[85,1113,186],{"emptyLinePlaceholder":185},[85,1115,1116],{"class":87,"line":100},[85,1117,1118],{},"fn read_config(path: &str) -> Result\u003CString> {\n",[85,1120,1121],{"class":87,"line":194},[85,1122,1123],{},"    let content = std::fs::read_to_string(path)\n",[85,1125,1126],{"class":87,"line":200},[85,1127,1128],{},"        .with_context(|| format!(\"Failed to read config from {path}\"))?;\n",[85,1130,1131],{"class":87,"line":206},[85,1132,1133],{},"    Ok(content)\n",[85,1135,1136],{"class":87,"line":397},[85,1137,103],{},[267,1139,1140,1154],{},[270,1141,1142,1145,1146,1149,1150,1153],{},[28,1143,1144],{},"Result\u003CT>","（这里为 ",[28,1147,1148],{},"anyhow::Result\u003CT>","）是 ",[28,1151,1152],{},"Result\u003CT, anyhow::Error>"," 的别名。",[270,1155,1156,875,1159,1162],{},[28,1157,1158],{},"with_context",[28,1160,1161],{},"context"," 方法为错误附加人类可读的上下文，在最终打印错误链时非常有用。",[24,1164,1165,1166,1169,1170,539],{},"对于应用入口，通常会有一个 ",[28,1167,1168],{},"main"," 返回 ",[28,1171,1172],{},"anyhow::Result\u003C()>",[77,1174,1176],{"className":79,"code":1175,"language":12,"meta":81,"style":81},"fn main() -> anyhow::Result\u003C()> {\n    let config = read_config(\"config.toml\")?;\n    \u002F\u002F ...\n    Ok(())\n}\n",[28,1177,1178,1183,1188,1193,1198],{"__ignoreMap":81},[85,1179,1180],{"class":87,"line":88},[85,1181,1182],{},"fn main() -> anyhow::Result\u003C()> {\n",[85,1184,1185],{"class":87,"line":94},[85,1186,1187],{},"    let config = read_config(\"config.toml\")?;\n",[85,1189,1190],{"class":87,"line":100},[85,1191,1192],{},"    \u002F\u002F ...\n",[85,1194,1195],{"class":87,"line":194},[85,1196,1197],{},"    Ok(())\n",[85,1199,1200],{"class":87,"line":200},[85,1201,103],{},[24,1203,1204,1205,1169,1207,987,1209,1211],{},"如果 ",[28,1206,1168],{},[28,1208,218],{},[28,1210,1085],{}," 自动以漂亮格式打印错误，包括所有上下文和原因链。",[64,1213,1214],{"id":1214},"日志记录与错误上下文",[24,1216,1217,1218,1221,1222,1225,1226,1228,1229,1231],{},"在传播错误的同时，可能还需要记录日志（如 ",[28,1219,1220],{},"log"," + ",[28,1223,1224],{},"env_logger","）。可在 ",[28,1227,563],{}," 或与 ",[28,1230,1085],{}," 的 context 结合时执行。但要注意：不要既记录又传播，通常选择在较高层统一记录并退出，避免重复日志。",[24,1233,1234,1236],{},[28,1235,563],{}," 可以转换错误类型的同时添加信息：",[77,1238,1240],{"className":79,"code":1239,"language":12,"meta":81,"style":81},"let f = File::open(\"data.txt\")\n    .map_err(|e| format!(\"启动失败: {e}\"))?;\n",[28,1241,1242,1247],{"__ignoreMap":81},[85,1243,1244],{"class":87,"line":88},[85,1245,1246],{},"let f = File::open(\"data.txt\")\n",[85,1248,1249],{"class":87,"line":94},[85,1250,1251],{},"    .map_err(|e| format!(\"启动失败: {e}\"))?;\n",[24,1253,1254,1255,1257,1258,1261],{},"对于 ",[28,1256,1085],{},"，直接使用 ",[28,1259,1260],{},".context(\"...\")"," 就能添加上下文，无需手动进行字符串转换。",[24,1263,1264,1265,1267,1268,1270,1271,1273],{},"综合来说，一个稳健的 Rust 项目会在库层使用 ",[28,1266,637],{}," 定义细粒度错误，在应用层使用 ",[28,1269,1085],{}," 快速传播错误并附加上下文，在必要时通过 ",[28,1272,30],{}," 处理不可恢复场景。这种分层带来了清晰、安全且易于调试的错误管理体系。",[41,1275,1276],{"id":1276},"参考文献",[1278,1279,1280,1293],"table",{},[1281,1282,1283],"thead",{},[1284,1285,1286,1290],"tr",{},[1287,1288,1289],"th",{},"资料",[1287,1291,1292],{},"说明",[1294,1295,1296,1311,1322],"tbody",{},[1284,1297,1298,1308],{},[1299,1300,1301],"td",{},[1302,1303,1307],"a",{"href":1304,"rel":1305},"https:\u002F\u002Fdoc.rust-lang.org\u002Fbook\u002Fch09-00-error-handling.html",[1306],"nofollow","Error handling",[1299,1309,1310],{},"官方书第 9 章",[1284,1312,1313,1319],{},[1299,1314,1315],{},[1302,1316,637],{"href":1317,"rel":1318},"https:\u002F\u002Fdocs.rs\u002Fthiserror\u002F",[1306],[1299,1320,1321],{},"库错误派生",[1284,1323,1324,1330],{},[1299,1325,1326],{},[1302,1327,1085],{"href":1328,"rel":1329},"https:\u002F\u002Fdocs.rs\u002Fanyhow\u002F",[1306],[1299,1331,1332],{},"应用错误传播",[1334,1335,1336],"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 .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":81,"searchDepth":94,"depth":94,"links":1338},[1339,1349,1359,1367,1373],{"id":43,"depth":94,"text":1340,"children":1341},"5.1 不可恢复错误与 panic!",[1342,1344,1346,1347],{"id":66,"depth":100,"text":1343},"panic! 的使用",{"id":113,"depth":100,"text":1345},"调试栈信息：RUST_BACKTRACE",{"id":157,"depth":100,"text":157},{"id":237,"depth":100,"text":1348},"与 abort 的区别",{"id":284,"depth":94,"text":1350,"children":1351},"5.2 可恢复错误与 Result",[1352,1353,1354,1356,1357],{"id":296,"depth":100,"text":296},{"id":351,"depth":100,"text":351},{"id":439,"depth":100,"text":1355},"? 运算符",{"id":531,"depth":100,"text":531},{"id":633,"depth":100,"text":1358},"自定义错误类型与 thiserror",{"id":851,"depth":94,"text":1360,"children":1361},"5.3 Option 与缺失值处理",[1362,1363,1365],{"id":867,"depth":100,"text":867},{"id":951,"depth":100,"text":1364},"与 Result 互相转换",{"id":999,"depth":100,"text":1366},"使用 ? 处理 Option",{"id":1047,"depth":94,"text":1048,"children":1368},[1369,1370,1372],{"id":1062,"depth":100,"text":1062},{"id":1081,"depth":100,"text":1371},"应用程序的错误处理：anyhow",{"id":1214,"depth":100,"text":1214},{"id":1276,"depth":94,"text":1276},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fend\u002FRust\u002Ferror","knowledge\u002Fknowledge\u002Fbooklet\u002Fend\u002FRust\u002Ferror.md","md",{"items":1379},[1380,1384,1388,1392,1396,1400],{"path":1381,"title":1382,"description":1383,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fdocs","第八章：测试、文档与质量","Rust 将测试和文档视为语言的一等公民。通过内置的测试框架，你可以在项目里编写单元测试、集成测试以及随文档一起运行的示例测试，三者共用 cargo test 命令。配合 cargo doc 生成文档，形成了一套确保代码质量与可维护性…",{"path":1385,"title":1386,"description":1387,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ft","泛型与 Trait","泛型和 trait 是 Rust 实现代码复用与多态的两大支柱。前置：Rust 基础。泛型让代码可以工作在多种类型上而不牺牲性能，trait 定义了类型间的共享行为，二者结合形成了零成本抽象的强大表达能力。本章将深入泛型定义、trai…",{"path":1389,"title":1390,"description":1391,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Ftype","第二章：基础语法与类型","Rust 的类型系统和语法设计处处体现着“安全”与“显式”的理念。这一章你将掌握变量绑定、基本类型、复合类型、函数定义以及所有基础控制流结构，它们是你写出任何 Rust 程序的基石。",{"path":1393,"title":1394,"description":1395,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fpackage","第七章：模块系统与包管理","Rust 的模块系统为代码组织、封装和复用提供了一套严谨但灵活的机制。包、crate、模块以及 use 路径相互配合，让你能够把项目拆解成清晰的功能单元，同时精确控制哪些对外可见。本章将带你系统掌握这些构建大型 Rust 项目所必需的…",{"path":1397,"title":1398,"description":1399,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fother","第十章：进阶特性与模式","Rust 的核心安全保证覆盖了绝大多数日常编程场景。但当你需要打破常规——无论是编写极致通用的抽象、与 C 库交互，还是内联优化——本章将带你进入 Rust 的深层能力：声明宏与过程宏、unsafe 的超能力与封装、高级类型系统技巧…",{"path":1401,"title":1402,"description":1403,"category":10,"score":206},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fcore","第三章：所有权、借用与生命周期（核心）","所有权系统是 Rust 最独特、最核心的语言特性。它让 Rust 无需垃圾回收器就能保证内存安全，并在编译期消除数据竞争。本章你将深入理解所有权如何运转、引用和借用如何被检查、生命周期如何标注，以及常见智能指针如何扩展所有权模型。",{"series":1405,"title":12,"items":1406,"index":200,"prev":1432,"next":1433},"booklet\u002Fend\u002Frust",[1407,1411,1415,1417,1421,1423,1424,1426,1428,1430],{"path":1408,"title":1409,"date":1410,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fasync","第九章：并发与异步","2020-05-22",{"path":1412,"title":1413,"date":1414,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fbase","Rust 基础","2020-07-21",{"path":1401,"title":1402,"date":1416,"pinned":15},"2023-05-09",{"path":1418,"title":1419,"date":1420,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Frust\u002Fdata-type","第四章：复合数据类型","2021-02-24",{"path":1381,"title":1382,"date":1422,"pinned":15},"2026-01-11",{"path":7,"title":8,"date":14,"pinned":15},{"path":1397,"title":1398,"date":1425,"pinned":15},"2023-12-29",{"path":1393,"title":1394,"date":1427,"pinned":15},"2024-05-26",{"path":1385,"title":1386,"date":1429,"pinned":15},"2024-10-31",{"path":1389,"title":1390,"date":1431,"pinned":15},"2024-08-31",{"path":1381,"title":1382,"date":1422,"pinned":15},{"path":1397,"title":1398,"date":1425,"pinned":15}]