Rust in Everything
基于 tenferro-runtime 0.8.0

cannot add &TracedTensor to Result

主线位置:traced 主线的第 1 步。TracedTensor 上的运算只往图里加节点,建图时就做形状和 dtype 检查,所以每个运算都返回 Result。为什么、哪些错误在建图时报,见 TracedTensor。

flowchart TB
  ab["a 加 b"] --> r["Result TracedTensor"]
  r --> plus["再加 c"]
  plus --> e0369["E0369 Result 无 Add"]
  r --> exp[".exp()"]
  exp --> e0599["E0599 Result 无 exp"]
  r --> q["先写 ?"]
  q --> ok["TracedTensor 继续算"]

实验 crate exp-tenferro8/pit1-pit,bin p09_chain(debug,编译失败)、p09_chain_fix(release);M2 Max、macOS 27、rustc 1.99.0。报错里 cargo 缓存和工具链的路径前缀写成 ...。

现象

照着写数学公式的习惯:

let a = TracedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
let b = TracedTensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
let c = TracedTensor::from_vec_col_major(vec![2], vec![5.0_f64, 6.0])?;
let sum = &a + &b + &c;
let y = (&a * &b).exp();
error[E0369]: cannot add `&TracedTensor` to `Result<TracedTensor, tenferro_runtime::Error>`
   --> src/bin/p09_chain.rs:7:21
    |
  7 |   let sum = &a + &b + &c;
    |             ------- ^ -- &TracedTensor
    |             |
    |             Result<TracedTensor, tenferro_runtime::Error>
    |
note: `Result<TracedTensor, tenferro_runtime::Error>` does not implement `std::ops::Add<&TracedTensor>`
...
error[E0599]: no method named `exp` found for enum `Result<T, E>` in the current scope
    --> src/bin/p09_chain.rs:8:21
     |
   8 |   let y = (&a * &b).exp();
     |                     ^^^
     |
note: the method `exp` exists on the type `TracedTensor`
    --> .../tenferro-runtime-0.8.0/src/traced.rs:1634:5
     |
1634 |     pub fn exp(&self) -> Result<TracedTensor> {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: there is a method `expect` with a similar name, but with different arguments
...
help: use the `?` operator to extract the `TracedTensor` value, propagating a `Result::Err` value to the caller
     |
   8 |   let y = (&a * &b)?.exp();
     |                    +

第一个运算 &a + &b 没问题,出错的是「拿它的结果接着算」。

出处

运算符实现在 traced.rs 开头附近,impl Add for &TracedTensor:

impl std::ops::Add for &TracedTensor {
    type Output = Result<TracedTensor>;

    fn add(self, rhs: &TracedTensor) -> Result<TracedTensor> {
        TracedTensor::add(self, rhs)
    }
}

Sub、Mul、Div、Rem、Neg 和 f64 缩放都一样,Output 是 Result<TracedTensor>。tenferro 没有给 Result<TracedTensor, Error> 实现这些运算符,所以 (&a + &b) + &c 没有实现可找。(孤儿规则并不禁止这么做,右操作数 &TracedTensor 是本地类型就够了;candle 就给 Result<Tensor> 实现了运算符,见怎么读这一卷。)exp 是 TracedTensor 的固有方法,Result 上当然没有。

这是有意的取舍。TracedTensor::add 的文档注释写着:「Tenferro prioritizes robust error handling over the conciseness of chained operator notation; the explicit fallible methods are the canonical form for longer sequences.」建图时会做广播形状检查、dtype 推断和提升(见 dot_general: dtype mismatch),任何一步都可能失败;运算符返回 TracedTensor 就只能在失败时 panic。直接运算(拿 &mut engine 的)和 eager 运算也都返回 Result,同样不能直接串。

修法

每一步写 ?,或者改用方法链:

let s1 = (&(&a + &b)? + &c)?;
let s2 = a.add(&b)?.add(&c)?;
let y = (&a * &b)?.exp()?;

编译、执行(GraphCompiler::new().compile 加注册了 CpuEngine 的 Runtime,见一次 grad):

s1 = [9.0, 12.0]
s2 = [9.0, 12.0]
y = [20.085536923187668, 2980.9579870417283]

长公式用方法链更好读:a.add(&b)?.add(&c)? 从左到右一眼能看出顺序,? 落在每个方法后面。只有一两个运算时运算符加一个 ? 也很清楚:(&x * &x)?。

在测试或原型里想先跑通,可以在每一步后面 .unwrap(),但出错时只会得到 panic 和一行 Err 的 Debug,丢掉了错误里的阶段等上下文;正式代码里用 ? 交给调用方。

怎么确认没中招

  • 报错里出现 Result<TracedTensor, tenferro_runtime::Error> 作为运算符的左操作数(E0369)或方法的接收者(E0599),就是少了一个 ?。rustc 的最后一条 help 会指出该加在哪;前面那条「there is a method expect」不用理。
  • 函数要用 ?,返回类型得是 Result<_, E> 且 E: From<tenferro_runtime::Error>;Box<dyn std::error::Error> 和 anyhow::Error 都满足。

小结

  • &TracedTensor 的 + - * / %、-x 都返回 Result<TracedTensor>;Result 上没有运算符,也没有张量方法。
  • 这是上游有意的选择:建图时的形状、dtype 错误要能传出来,而不是 panic。
  • 每一步写 ?,长链用 a.add(&b)?.add(&c)?。

💬 关联讨论

发起讨论
加载中...