matklad / xshell

Apache License 2.0
675 stars 28 forks source link

Interiour mutability seems unnecessary #66

Open WaffleLapkin opened 1 year ago

WaffleLapkin commented 1 year ago

At the time of writing this, xshell (v0.2.3) uses interiour mutability inside the Shell: https://github.com/matklad/xshell/blob/af75dd49c3bb78887aa29626b1db0b9d0a202946/src/lib.rs#L381-L384

To me, this seems unnecessary & error prone.

The path value is mutated in

Push* guards are a bit more tricky to re-design, but they are also IMO the most error prone part — guards being disconnected from the object they are guarding seems fragile (although I can't come up with a really bad example, so maybe I'm imagining...).

Instead I think it would be better and more "rusty" to either

  1. Store a &mut Shell inside of Push* and implement DerefMut<Target = Shell> — this still feels a little bit weird, because you can still mutate the guarded object, but at least it uses no interiour mutability
  2. Implement all the methods on Push*, such that they don't even need to mutate Shell (possibly use a trait such that you can nest Push*es) — IMO the nicer approach, but requires more design work

In both cases the way you use push_dir/push_env changes to be more akin to mutexes for example:

// old
let _guard = sh.push_dir(user);
cmd!(sh, "git clone https://github.com/{user}/{repo}.git").run()?;

// new
let sh = sh.push_dir(user);
cmd!(sh, "git clone https://github.com/{user}/{repo}.git").run()?;

Either way this is just something that came to my mind while I was using xshell. I want to design/implement those API changes, my question thus is: would you like for these changes to be upstreamed, or should I create a fork? :)

matklad commented 1 year ago

Yeah, that's tricky, some thoughts:

:thinking:

I wonder if the solution here is to be dumber, rather than to be smarter?

impl Shell {
    fn push_dir(&self, dir) -> Shell {
        let mut new_sh = self.clone();
        new_sh.dir = dir;
        new_sh
    }
}