Tch-rs Allows a tensor that is not mutable to change

Why does tch-rs allow a tensor that is not mutable to change ? Here is a simple example:

Cargo.toml

[package]
name = "use-tch-rs"
version = "0.1.0"
edition = "2024"

[dependencies]
tch = { version = "0.24.0", features = ["download-libtorch"] }

src/main.rs

fn main() {
    use tch::Tensor;
    //
    let tensor = Tensor::zeros(&[2, 2], (tch::Kind::Float, tch::Device::Cpu));
    println!( "{}", tensor );
    let mut element = tensor.get(0).get(0); 
    let _ = element.fill_(6.0);
    println!( "{}", tensor );
}

cargo run

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/use-tch-rs`
[[0., 0.],
 [0., 0.]]
Tensor[[2, 2], Float]
[[6., 0.],
 [0., 0.]]
Tensor[[2, 2], Float]

tch is a crate wrapping the libtorch C++ library. I'm no FFI expert, but I assume this is fine as Rust's aliasing and mutability guarantees do not extend into C++ memory.

I was hoping that tch-rs would make const and mutable (and hence parallel programming) simpler. Here is a similar example in C++ libtorch:

main.cpp

#include <torch/torch.h>
int main() {
    const at::Tensor tensor = torch::zeros({2,2});
    std::cout << "tensor =\n" << tensor << std::endl;
    at::Tensor       other  = tensor;
    other [0][0]            = 5.0;
    std::cout << "tensor =\n" << tensor << std::endl;
    return 0;
}

Output

tensor =
 0  0
 0  0
[ CPUFloatType{2,2} ]
tensor =
 5  0
 0  0
[ CPUFloatType{2,2} ]

there's a choice for the api design, and apparently the author of tch-rs chose to mimic the C++ api. quoting the very first paragraph of the readme file:

The goal of the tch crate is to provide some thin wrappers around the C++ PyTorch api (a.k.a. libtorch). It aims at staying as close as possible to the original C++ api. More idiomatic rust bindings could then be developed on top of this.

if you don't like the api design of this libtorch binding crate, an alternative to tch is candle, which should feel more idiomatic.

note, the implementation of candle still has reference semantics and interior mutability, it uses internal locks (an Arc<RwLock<...>>, to be specific) , similar to libtorch, but the api is mostly value based. it actually takes non trivial effort to do in-place modifications.