
1.6. 错误处理1.6.1. 一种错误类型RustyML只有一种错误类型rustyml::error::Error。RustyML里每一个可能失败的操作都返回ResultT, rustyml::error::Error它还有一个别名pub type RustymlResultT std::result::ResultT, Error;Error不在prelude里错误相关的内容要单独引入use rustyml::error::Error;以及其他内容。 以下是Error的变体变体触发场景Display信息{}/to_string()EmptyInput(String)需要数据的地方传入了空数组、空向量或空数据集input is empty: whatDimensionMismatch { expected, found }两个标量计数对不上dimension mismatch: expected e, found fShapeMismatch { expected, found }两个张量的形状对不上梯度与它流入的那个激活值shape mismatch: expected [..], found [..]NonFinite(String)数据中或计算产出的某个值为NaN/infnon-finite value (NaN or infinity) encountered in whereInvalidParameter { name, reason }用户传入的超参数超出取值范围invalid parameter name: reasonInvalidInput(String)没有更具体变体可用时的校验失败rank 不对、样本太少invalid input: msgNotFitted(static str)在fit之前调用了需要已训练模型的方法model name has not been fitted; call fit before this operationNotConverged(String)迭代算法始终未达到收敛条件failed to converge: msgComputation { context, source }数值崩溃、不变量被破坏或包装了一个外部错误computation failed: contextNeuralNetwork(NnError)神经网络特有的失败透明转发自NnErrorTree(TreeError)决策树特有的失败透明转发自TreeErrorIo(IoError)文件系统或反序列化失败透明转发自IoError需要注意的是DimensionMismatch比较的是标量计数比如特征数、向量长度。而ShapeMismatch针对的问题是整个张量的形状不一致主要出现在神经网络代码里。Error标注了#[non_exhaustive]这要求在对错误进行match时必须带一个通配_ 或Err(e) 分支。1.6.2. 子错误Error的三个变体各自包装了一个更小的枚举。只与神经网络相关的问题层状态、权重形状、编译和只与树相关的问题分类还是回归都待在各自的枚举里。NnError位于rustyml::neural_network::NnError包含ForwardPassNotRun(static str)WeightShape { name, expected, found }NotCompiled(static str)EmptyModel。代码例userustyml::neural_network::sequential::Sequential;userustyml::neural_network::layers::Dense;userustyml::neural_network::layers::activation::ReLU;userustyml::neural_network::NnError;userustyml::error::Error;usendarray::Array;fnmain(){letmutmodelSequential::new();model.add(Dense::new(4,2,ReLU::new()).unwrap());letxArray::ones((3,4)).into_dyn();letyArray::ones((3,2)).into_dyn();// 没有调用 compile()所以还没配置优化器和损失函数matchmodel.fit(x,y,1){Ok(_)unreachable!(training should not have started),Err(Error::NeuralNetwork(NnError::NotCompiled(missing))){println!(compile the model first: {missing} is not specified);}Err(e)println!(unexpected: {e}),}}TreeError位于rustyml::machine_learning::TreeError有以下两个变体:NotClassificationTreeCorruptStructure(static str)代码例userustyml::machine_learning::{Algorithm,DecisionTree,TreeError};userustyml::error::Error;usendarray::array;fnmain(){// 回归树is_classifier false没有各类别的概率lettreeDecisionTree::new(Algorithm::CART,false).unwrap();letxarray![[1.0,2.0]];matchtree.predict_proba(x){Err(Error::Tree(TreeError::NotClassificationTree)){println!(predict_proba is classification-only);}otherprintln!(unexpected: {other:?}),}}IoError位于rustyml::error::IoError有四个变体Std(std::io::Error)对应文件系统失败Serialization(postcard::Error)对应二进制格式RustyML用postcard序列化ModelStructureMismatch(String)对应加载的神经网络文件与目标架构对不上的情况层数不同、某个位置的层类型不同或某个权重的形状放不进目标层UnsupportedModelFormat(String)对应这个文件根本不是RustyML模型文件或者它的磁盘格式版本不是当前构建写出的那个版本代码例userustyml::machine_learning::LinearRegression;userustyml::error::{Error,IoError};fnmain(){matchLinearRegression::load_from_path(model_that_does_not_exist.bin){Ok(_)unreachable!(the file should not exist),Err(Error::Io(IoError::Std(io_err))){// io_err 是底层的 std::io::Error这里的 kind 是 NotFound。println!(filesystem error: {io_err});}Err(Error::Io(IoError::Serialization(e))){println!(the file exists but is not a valid model: {e});}Err(e)println!(unexpected: {e}),}}序列化格式与版本控制详见7.2. 深入模型持久化。1.6.3. 匹配具体的变体最日常的失败是在fit之前就调用predict这会导致返回Error::NotFitted并把自己的名字作为static str带上userustyml::machine_learning::LinearRegression;userustyml::error::Error;usendarray::array;fnmain(){// 已构造但从未训练letmodelLinearRegression::new(true);letxarray![[1.0,2.0],[3.0,4.0]];matchmodel.predict(x){Ok(preds)println!({preds:?}),Err(Error::NotFitted(name)){println!({name} was not fitted; call fit() first);}Err(Error::DimensionMismatch{expected,found}){println!(wrong feature count: model wants {expected}, got {found});}// Error是#[non_exhaustive]所以通配分支是强制的Err(e)println!(other error: {e}),}}DimensionMismatch分支放在这里是为了展示写法这次调用实际触发的是NotFitted。但如果给一个已训练的模型进列数不对的矩阵走的就是第二个分支了此时expected是fit时看到的特征数found是传进predict的那个。1.6.4. 用?传播RustyML整个库只使用一种错误类型所以一整个管线上的错误都可以作为Error返回除了Result和?之外什么都不需要userustyml::machine_learning::{LinearRegression,RegularizationType};userustyml::error::RustymlResult;usendarray::{array,Array1,Array2};fntrain_and_predict(x:Array2f64,y:Array1f64)-RustymlResultArray1f64{// 下面每个 ? 都会从一次可能失败的调用中抬出一个 rustyml::error::ErrorletmutmodelLinearRegression::new(true).with_regularization(RegularizationType::L2(0.01))?;// 可能是 InvalidParametermodel.fit(x,y)?;// 可能是 EmptyInput / DimensionMismatch / NonFiniteletpredsmodel.predict(x)?;// 可能是 NotFitted / DimensionMismatchOk(preds)}fnmain(){letxarray![[1.0],[2.0],[3.0]];letyArray1::from_vec(vec![2.0,4.0,6.0]);matchtrain_and_predict(x,y){Ok(preds)println!(got {} predictions,preds.len()),Err(e)eprintln!(pipeline failed: {e}),}}当你确实需要汇报外部错误来自标准库或别的 crate但是又想使用进这套错误处理体系、同时保留它的成因链时就用Context扩展trait需要把这个trait导入到作用域。它为任何满足Send Sync static且实现了std::error::Error的ResultT, E都做了实现因此能和?配合。context会立即取用信息with_context接收一个只在错误路径上运行的闭包只要构造信息会带来分配凡是用到format!的就优先用闭包形式这样成功路径就不用执行闭包userustyml::error::{Context,Error,RustymlResult};fnparse_threshold(raw:str)-RustymlResultf64{// 一个标准库的 ParseFloatError连同我们的 context 一起包装成 Error::Computation// 它的 source() 链得以保留供之后向下转型使用。letvalue:f64raw.parse().with_context(||format!(parsing threshold from {raw:?}))?;Ok(value)}fnmain(){matchparse_threshold(not-a-number){Ok(v)println!(threshold {v}),Err(Error::Computation{context,source}){println!({context});ifletSome(cause)source{println!( caused by: {cause});}}Err(e)println!(unexpected: {e}),}}外部错误会成为Error::Computation的source可以经由标准的std::error::Error::source()链拿到并向下转型回原本的具体类型不丢失任何信息。1.6.5. 及早校验RustyML错误处理设计是任何接收超参数的入口都会及早校验并返回Result而不是在遇到非法输入时panic。userustyml::machine_learning::LinearRegression;userustyml::machine_learning::linear_model::LeastSquaresSolver;userustyml::error::Error;fnmain(){// learning_rate必须为正且有限// 0.0会返回错误matchLinearRegression::new(true).with_solver(LeastSquaresSolver::GradientDescent{learning_rate:0.0,max_iter:1000,tol:1e-6,}){Ok(_)unreachable!(a zero learning rate must not be accepted),Err(Error::InvalidParameter{name,reason}){// bad parameter learning_rate: must be positive and finite, got 0println!(bad parameter {name}: {reason});}Err(e)println!(unexpected: {e}),}}有些地方还是会直接panicmetrics与math模块的函数在遇到错误时直接 panic而不返回Result这是为了保持模块的轻量化。RustyML之外的ndarray操作是返回Result还是直接panic是ndarray决定的RustyML无法干涉。