Error ergonomics

#21

It is relevant in that the user’s Ok(expr?) problem is solved by catching functions.

#22

You are right about the stack trace. panic!() is the right solution in this case.

It would be nice to have “magic constants” (like __FILE__ and __LINE__ in PHP) to be able to include them in the error message, but this is another story. :slight_smile:

My point in having just an “automatic sum type including all the errors” was exactly to not having to convert them to other errors (losing the information about the original error), either automatically or semi-explicitly, but also to allow me to catch them where they happen, or just delegate that to the caller.

That’s why I said error-chain was the best solution (at least in my “app, not library” case): it allows me to wrap the error and bubble it up, or to transform it into another error. What’s missing is the ability to easily maintain the original error: I have to list them all in the foreign_links, thus effectively transforming them. It’s just easier to wrap them.

I am following the catch functions discussion, even if it’s quite hard for me, not having compilers theory knowledge, and not knowing C++, C# or Haskell. I can only discuss it from an end-user point of view.

I disagree that adding sugar to function signatures is easier than having an automatic handling of all types implementing a trait.

The Result<T, impl Error> I was speaking about earlier keeps the explicitness (seeing Ok(T) and Err(e) in the code) and avoids Ok(function_call()?) where the result is not guaranteed to be Ok at all. :slight_smile:

#23

I fail to understand what the “Ok(expr?)” problem is. Only thing I have found mentionned is that Alex-PK finds it ugly. Rust should try not to look ugly, but all taste is acquired. As has been expressed, it is the plainest way to “simply return the result of a fallible function call within another”. @Alex-PK, do you have a more precise way to phrase what you mean by “ugly”? @withoutboats, do you see anything else in the “Ok(expr?)” problem

If ugly means that the connotations of Ok and ? conflict into a “definitely maybe” mess, maybe the best approach for syntax hilighting for ?-ed expressions, to make clear what the order of operations is: the whole line is Ok as long as expr is.

If ugly means that the solution feels like a gotcha where discoverability is not enough, then arguably improving the compiler’s error message (and adding an autocorrection in the IDE) is the most direct solution.

#24

It’s been cited in the “catching functions” thread too, and in a few Rust2018 blog posts, from what I saw.

It’s ugly for two reasons: one is the one you say: is it ok or not?

The other is that it hides the “non happy path”. While reading the code it appears to be the correct result, but in effect it hides the possibility of an error.

And, to take my original problem, look at this:

pub fn render_page(&self, tpl_type: &str, data: &Object) -> Result<String> {
	Ok(self.engine.read().unwrap()
		.render(tpl_type, data)
		.context(ErrorKind::Error(format!("Unable to render template {}", tpl_type)))?)
}

It’s, at least, a code smell. Removing the whole Ok( and ?) makes it much more readable, and quickly glancing at the signature makes it clear that it can return an error, while the Ok makes it look like it won’t.

You can mitigate it by assigning to a temp var:

pub fn render_page(&self, tpl_type: &str, data: &Object) -> Result<String> {
	let res = self.engine.read().unwrap()
		.render(tpl_type, data)
		.context(ErrorKind::Error(format!("Unable to render template {}", tpl_type)))?;

	Ok(res)
}

But don’t you think this is simpler and more readable?

pub fn render_page(&self, tpl_type: &str, data: &Object) -> Result<String> {
	self.engine.read().unwrap()
		.render(tpl_type, data)
		.context(ErrorKind::Error(format!("Unable to render template {}", tpl_type)))
}
#25

In this precise case, in current Rust, I’d write the following which preserves the method-chaining style of the rest of the fuction.

pub fn render_page(&self, tpl_type: &str, data: &Object) -> Result<String> {
	self.engine.read().unwrap()
		.render(tpl_type, data)
		.context(ErrorKind::Error(format!("Unable to render template {}", tpl_type)))
                .map_err(From::from)
}

I see now that map_err(From::from) looks like a type-checking tax, but I prefer paying that tax to any solution with “spooky type coercion at a distance”. At least, you only have to learn it in one context.

#26

Did you know https://doc.rust-lang.org/std/macro.line.html and https://doc.rust-lang.org/std/macro.file.html?

3 Likes
#27

Is there an RFC for this?

#28

Not yet. I wanted to look at dynamic types as a whole before proposing any particular new features.

#29

It’s like (expr,).0 or |x| expr(x) – it’s something that sure looks like it does the same thing as just expr, but doesn’t because of something subtle. Compare Ok(expr)?, which is very similar, but actually is the same as just expr

#30

No, I didn’t! That’s great, thanks! :slight_smile:

#31

Interesting: comparing the four cases I can think of: |x| expr(x) , (expr,).0, Ok(expr?) but also &*expr, I have a different esthetic judgement on each:

  • |x| x.meth() versus a bare meth is a consequence of having methods at all, so the necessity of it should be clear from the context in which it’s used. Thus, I don’t see it as too much of a wart.
  • (expr,).0 on the other hand does not read well at all. I’m not even sure when you would use it, do you have an example?
  • &*expr is a necessary evil, which can be justified by invoking Rust’s Unique Ownership System®
  • concerning Ok(expr?), the context of a -> Result<T,E> function makes me aware of the need to go from one type of error to another, so it falls to me in the same category as |x| x.meth(), but I can see how it can seem closer to &*expr or (expr,).0.

I’d like to see if having it highlighted as Ok(expr?), with the expr? in a “faillible” color makes it more palatable.

#32

It seems to me like a case could be made for an assertion-like macro here that either evaluates to Ok(T) or throws in case expr is an Err:

Ok(expr?)

would be the same as:

verify!(expr)

maintain!(expr)

retain!(expr)

or using some new kind of operator:

expr!!
#33

I’m not sure this would help, TBH. As soon as you wrap multiple lines instead of a simple expr variable, it gets ugly soon. Both for the added parentheses and for the confusing keyword at the beginning of the computation.

The !! operator sounds more like “do this or panic” than “try to do it and return an error if it fails” (see Kotlin: http://kotlinprogrammer.blogspot.co.uk/2017/07/kotlin-double-exclamation-operator.html) :slight_smile:

#34

I’m not actually referring to methods at all, here, since yes, method-syntax-only autoref and |x| foo.bar(x) vs foo.bar certainly are bonus differences.

But even just for a plain old function, there’s still all the things from this thread:

And rust certainly has a bunch of these, but that doesn’t keep them from being really surprising when you first stumble over them.

Oh, another one: {x} vs x, which bluss wrote a great post about: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/

closed #35

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.