Skip to content

Instantly share code, notes, and snippets.

@icefoxen
Created February 14, 2019 19:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icefoxen/f5a781f5c38d73c108d8426da851d80c to your computer and use it in GitHub Desktop.
Save icefoxen/f5a781f5c38d73c108d8426da851d80c to your computer and use it in GitHub Desktop.

Forgive me. I tried to leave it there, I really did. But I can't resist the urge to just, you know, try to fix something. So I wanted to try to see if it was possible to make your Rust frustrations (Rustrations?) smoother.

First: braindead error incantation.

#[macro_export]
macro_rules! raise {
    ($e:expr) => {
        return Err(failure::Error::from($e));
    };
}

? calls .into() on the error type for you. Not using ?, you have to call .into() yourself. Sometype::from(x) is the same as let new_x: Sometype = x.into();. ...except when it isn't.

#[macro_export]
macro_rules! raise {
    ($e:expr) => {
        return Err($e.into());
    };
}

Second: Reborrowing clusterfuck

store.query_valid_paths(&mut paths.iter().map(|s| &**s));

Ok, so the question is "what's actually going on here?" paths is a Vec<String> and query_valid_paths takes Iterator<Item=&str>. These seem reasonable. What we WANT to do is just turn a Vec<String> into an Iterator<Item=&str>. paths.iter() gets us a Vec<&String>, and &String automatically turns into &str, right?

store.query_valid_paths(&mut paths.iter().map(|s| s.as_ref()));

Annoyingly, what DOESN'T happen is &String getting turned into &str. That DOES happen sometimes but it's heckin spotty; I think it only happens when you call a method, so if s is &String and you do s.foo() then it gets turned into s.as_ref().foo(). I think you can find the details somewhere in the docs of the Deref trait. ...or maybe the AsRef trait. Or is it the Borrow trait? it's a fucking mess.

There's several other variations that you would THINK might work but won't, like paths.iter().(|s| &*s) (I don't even know why this doesn't do the Right Thing, it gets you an &String out instead of a &str though), or paths.into_iter().map(|s| &s) or paths.into_iter().map(|s| s.as_ref()) (lifetime issues)

Part of the problem is also that * in Rust can do a type conversion, somethin called "deref coercions". This is used to take something sorta like a pointer, such as a Box or Rc, and treat it like a pointer. So when you're doing &* you're not saying "dereference this and immediately make a reference to it", which would be a no-op, you're saying "call a method on this that may return any ol' value, then make a reference to that", which is called "reborrowing". I used to use &* and variations of it a lot, and it is quite convenient once you train your eye to look for it, but these days I am increasingly prefering .as_ref() and such which are a hell of a lot easier to control.

...derp, that answers my question of why &*s doesn't get you the Right Thing. s => &String, *s => String, &*s => &String. BUT, **s => str; str is invalid to have on its own since its size is not known at compile time, but &**s => &str which is a string slice so that's just fine. UGH.

PLUS, doing foo.x() dereferences foo for you if necessary! So if Rust really gave you NO help, I'd have to write (*s).as_ref()! So there's a sneaky dereference hidden in there anyway... which might be a type conversion... but ONLY if it's a method call! But wait, no, as_ref() is implemented on &self, not self, so it take a reference to a String anyway, so I can call String::as_ref(s) and it's just fine but String::as_ref(&s) won't work!

FJKLDSA;JFDSA;JFLKDSA;JFK;LSD

THREE: More heckin' inconvenient error handling!

Can we make this a one-liner?

    fn create_directory(&mut self, path: &str) -> Result<()> {
        fs::create_dir(self.rooted(path)?)
        Ok(())
    }

Yes:

fn create_directory(&mut self, path: &str) -> Result<()> {
    fs::create_dir(self.rooted(path)?).map_err(|e| e.into())
}

Again, ? calls .into() on the error type for you. Except fs::create_dir() returns a Result so you have to do map_err() to JUST convert the error type. Apparently you can also do .map_err(Into::into) and it will just find the right .into() implementation itself.

FOUR: HECKIN LONG CONVERSIONS

let target = std::fs::read_link(entry.path()).unwrap().to_str().unwrap().to_owned();

srsly, what the hell?

Ok, we want target to be a String. std::fs::read_link() returns std::result::Result<std::path::PathBuf, std::io::Error>. So we need to get a String out of this. PathBuf is an owned path, and a path is just a string anyway, so this should be easy, right? Why do we need two conversions?

Basically, 'cause we can't have nice things. Rust's path handling is a shitty compromise. A String is a UTF-8 string. There is no such thing as a String that contains invalid UTF-8, more or less. But a Path is whatever the OS uses for paths, which are OS-native strings... What Rust calls OsString. On Unix, these are zero-delimited ASCII-ish crap. On Windows, these are zero-delimited UTF-16 crap. So if you want to do the SIMPLE AND OBVIOUS thing of turning a Path into a String you have to say "Is this valid UTF-8? Are you sure? Ok, copy the heckin' thing into something I can actually touch."

See my feelings on this here.

You are, unfortunately, doing the Right Thing here; I think Rust is doing the Wrong Thing. Rust is all about the zero-cost abstractions but really that just means when there's an abstraction that the language can't paper over, the effort of fixing it gets shoved onto the programmer. I really need to write a library that handles paths more sanely, adding the tiny amount of runtime overhead that treating them like native strings incurs and just sucking it up so you can treat them like what you actually want to, lists of path components. Maybe there's a library for this already?

FIVE: RUSTFMT STUPIDITY

return protocol_error!(
    "expected {} (hex {:02x?}), got {} (hex {:02x?})",
    v,
    v,
    r,
    r
);

Siiiiiigh. Yeah, I feel you there. You're not the only one. idk, buy a taller screen?

ok I need to go lie down a sec

So yeah. Rust is a weird mish-mash of the language trying to help you take shortcuts, and the language forcing you to take long-cuts. And the result ends up complicated enough that you need a heckin' mining helmet and pickaxe to get to the bottom of it. Maybe it's time for me to take a serious look at Zig...

@kennethuil
Copy link

You should be able to do:

Ok(fs::create_dir(self.rooted(path)?)?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment