matklad / xshell

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Interiour mutability seems unnecessary

WaffleLapkin opened this issue · comments

At the time of writing this, xshell (v0.2.3) uses interiour mutability inside the Shell:

xshell/src/lib.rs

Lines 381 to 384 in af75dd4

pub struct Shell {
cwd: RefCell<PathBuf>,
env: RefCell<HashMap<OsString, OsString>>,
}

To me, this seems unnecessary & error prone.

The path value is mutated in

  • Shell::change_dir (it could just as well accept &mut self I don't immediately see the need for IM here)
  • Shell::set_var (again, could accept &mut self)
  • PushDir::new/PushDir::drop and PushEnv::new/PushEnv::drop

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? :)

Yeah, that's tricky, some thoughts:

  • part of the motivation for the current design is that threading &Shell is more pleasant than threading &mut Shell.

  • I don't want to have complex API, and traits or deref are rather complex.

  • In general, I am not quite happy with push_dir API,

    {
        let _dir = sh.push_dir("./bla")
        ...
    }
    

    looks quite noisy at the call-site

🤔

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
    }
}