Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interiour mutability seems unnecessary #66

Open
WaffleLapkin opened this issue Mar 23, 2023 · 1 comment
Open

Interiour mutability seems unnecessary #66

WaffleLapkin opened this issue Mar 23, 2023 · 1 comment

Comments

@WaffleLapkin
Copy link

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

@matklad
Copy link
Owner

matklad commented Mar 23, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants