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.
#[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());
};
}
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
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.
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?
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?
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...
You should be able to do:
Ok(fs::create_dir(self.rooted(path)?)?)