Live data from Hacker News

Rust for C++ Programmers Part 7: Data Types

featherweightmusings.blogspot.com

11–20 of 39 posts

Re: Rust for C++ Programmers Part 7: Data Types

#11
post #5

I would love to see an article like this for error handling in Rust. I was really interested in using conditions, but those were apparently backed out. Which leaves error handling through return values and macros. This seems like a step back from Exceptions to me. I want to be convinced otherwise, but I'm struggling to see how this is better than other mechanisms.

  > This seems like a step back from Exceptions to me. I 
  > want to be convinced otherwise, but I'm struggling to 
  > see how this is better than other mechanisms.
In a low-level language, guaranteeing memory safety in the face of resumable exceptions would be a nightmare. See Graydon's original post on the choice to avoid exceptions:

https://mail.mozilla.org/pipermail/rust-dev/2013-April/00381...

Selected quote:

  > In particular, to summarize for the impatient: once you get resumable 
  > exceptions, your code can only be correct if it leaves every data 
  > structure that might persist through an unwind-and-catch (that it 
  > acquired through &mut or @mut or the like, and live in an outer frame) 
  > in an internally-consistent state, at every possible exception-point.

  > I.e. you have to write in transactional/atomic-writes style in order to 
  > be correct. This is both performance-punitive and very hard to get 
  > right. Most C++ code simply isn't correct in this sense. Convince 
  > yourself via a quick read through the GotWs strcat linked to:

  > http://www.gotw.ca/gotw/059.htm
  > http://www.gotw.ca/gotw/008.htm
For more on the topic of exception-safety in C++, see the following paper by Bjarne Stroustrup:

http://www.stroustrup.com/except.pdf

I don't think that Rust's error handling solution is ideal, but I think that it might be approaching the best possible solution for its chosen context. Error handling is a hard problem!

Re: Rust for C++ Programmers Part 7: Data Types

#12
post #5

I would love to see an article like this for error handling in Rust. I was really interested in using conditions, but those were apparently backed out. Which leaves error handling through return values and macros. This seems like a step back from Exceptions to me. I want to be convinced otherwise, but I'm struggling to see how this is better than other mechanisms.

Error handling in Rust is actually pretty awesome. There's a standard `Result ` type. One can write a potentially fail-able function like: fn can_fail(arg: bool) -> Result { if arg { Ok(()) } else { Err(StrBuf::from_str("Oops! Something went wrong.")) } } Here, there's no value when the function succeeds `()`. When it fails (I don't mean fail as in a `fail!()` or a panic or anything), you get a string back. You can p…

Another really great thing about returning a result Result is that your caller code will then must use this return value or otherwise a warning will be emitted by the compiler at compile time. For those interested, more details are provided in the core lib documentation: http://doc.rust-lang.org/core/result/

Re: Rust for C++ Programmers Part 7: Data Types

#13
post #11
post #5

I would love to see an article like this for error handling in Rust. I was really interested in using conditions, but those were apparently backed out. Which leaves error handling through return values and macros. This seems like a step back from Exceptions to me. I want to be convinced otherwise, but I'm struggling to see how this is better than other mechanisms.

> This seems like a step back from Exceptions to me. I > want to be convinced otherwise, but I'm struggling to > see how this is better than other mechanisms. In a low-level language, guaranteeing memory safety in the face of resumable exceptions would be a nightmare. See Graydon's original post on the choice to avoid exceptions: https://mail.mozilla.org/pipermail/rust-dev/2013-April/00381... Selected quote: > In par…

One last thing that deserves to be mentioned: Rust does have unwinding-on-failure, which is similar to exceptions, with the restriction that unwinding can only be caught at task boundaries. This allows failure in a single component to be isolated and contained. The pertinent distinction here is that the unwinding is not resumable in the normal sense; at best, a parent task can detect that a child task has failed and attempt to restart the task, without having the ability to persist any of the failed task's state.

Re: Rust for C++ Programmers Part 7: Data Types

#14
post #5

I would love to see an article like this for error handling in Rust. I was really interested in using conditions, but those were apparently backed out. Which leaves error handling through return values and macros. This seems like a step back from Exceptions to me. I want to be convinced otherwise, but I'm struggling to see how this is better than other mechanisms.

Error handling in Rust is actually pretty awesome. There's a standard `Result ` type. One can write a potentially fail-able function like: fn can_fail(arg: bool) -> Result { if arg { Ok(()) } else { Err(StrBuf::from_str("Oops! Something went wrong.")) } } Here, there's no value when the function succeeds `()`. When it fails (I don't mean fail as in a `fail!()` or a panic or anything), you get a string back. You can p…

I tend to practice "only catch what you can handle" in exception-enabled languages - I haven't written in a systems language in almost a decade, mostly bad memories of C. How much does error-handling get in the way when you have to live without stack unwinding?

Re: Rust for C++ Programmers Part 7: Data Types

#16

I just read the first section, just the section on structs -- what's different here, from C? It provides all of the same features, with a slightly different syntax.

With structs, pretty much the only difference is the syntax. Enums are probably where the biggest differences are from C, as far as data types go.

Re: Rust for C++ Programmers Part 7: Data Types

#17
post #7

Earlier quoted context omitted.

For some reason I have an instinctual reaction that you should have to specify the type, but on reflection I'm not sure why. It is completely redundant, and since the fields aren't named for the purposes of a destructuring assignment like this any 2-field tuple is essentially equivalent. Hm.

Tuple structs aren't used often, but the whole point of them is to force you to name a type. The idea is to restrict the types that you can call a function with; it turns a given tuple from a structural type to a nominal type. For example, say you have two functions, where each function takes a single tuple of two floating point numbers: // Converts a Cartesian coordinate to a polar coordinate fn to_polar(coord: (f64…

Sure, and all of that makes sense for function arguments, but for a destructuring assignment I'm not sure the same constraints are meaningful.

Re: Rust for C++ Programmers Part 7: Data Types

#18
post #14

Earlier quoted context omitted.

Error handling in Rust is actually pretty awesome. There's a standard `Result ` type. One can write a potentially fail-able function like: fn can_fail(arg: bool) -> Result { if arg { Ok(()) } else { Err(StrBuf::from_str("Oops! Something went wrong.")) } } Here, there's no value when the function succeeds `()`. When it fails (I don't mean fail as in a `fail!()` or a panic or anything), you get a string back. You can p…

I tend to practice "only catch what you can handle" in exception-enabled languages - I haven't written in a systems language in almost a decade, mostly bad memories of C. How much does error-handling get in the way when you have to live without stack unwinding?

So, normally in Rust, it's no problem to ignore the return value of a function. However, some types are tagged with the `#[must_use]` attribute, which makes it a warning at compile time to ignore the return value of any function that returns that type. Take the following program, which writes a buffer of bytes directly to stdout:

  fn main() {
      let mut out = std::io::stdout();
      out.write([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]);
  }
The `.write()` method returns a Result type. The output of compiling this program:

  $ rustc pxtl.rs
  pxtl.rs:3:5: 3:53 warning: unused result which must be used, #[warn(unused_must_use)] on by default
  pxtl.rs:3     out.write([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Again, it's just a warning, so the program will compile and run as expected:

  $ ./pxtl
  Hello!
If you really don't care about the return value here, the simplest (and probably best) way of appeasing this warning is to explicitly ignore the return type by making use of pattern matching:

  fn main() {
      let mut out = std::io::stdout();
      let _ = out.write([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]);
  }
In Rust, the underscore is a pattern that means "I don't care about this thing, completely ignore it". The advantage of using the underscore here rather than an actual variable, e.g. `let x = out.write(...)`, is that it will be impossible to refer to the return value later on and thus explicitly expresses your intent to ignore it. (Furthermore, if you assigned the return value to a variable and then didn't use it later on, Rust would emit yet another warning, this time for having an unused variable.)

The warning message alludes to a second way of silencing this error, which is by sticking the `#[allow(unused_must_use)]` attribute on top of your function. This will silence any warnings that arise from that function. If you wanted to disable this warning for your entire program, you could instead stick the `#![allow(unused_must_use)]` global attribute at the top of your program. Alternatively, you could compile the program with the `--allow unused_must_use` flag to completely silence all warnings of this type.

(One final note: in all cases where you see word "allow" used above, if you replace it with "deny" it will turn the warning into a compile-time error, thus enabling you to enforce a more rigorous error-handling strategy if you so choose.)

Re: Rust for C++ Programmers Part 7: Data Types

#19
post #14

Earlier quoted context omitted.

Error handling in Rust is actually pretty awesome. There's a standard `Result ` type. One can write a potentially fail-able function like: fn can_fail(arg: bool) -> Result { if arg { Ok(()) } else { Err(StrBuf::from_str("Oops! Something went wrong.")) } } Here, there's no value when the function succeeds `()`. When it fails (I don't mean fail as in a `fail!()` or a panic or anything), you get a string back. You can p…

I tend to practice "only catch what you can handle" in exception-enabled languages - I haven't written in a systems language in almost a decade, mostly bad memories of C. How much does error-handling get in the way when you have to live without stack unwinding?

"only catch what you can handle" is incredible nonsense. Your code is the only code that knows how the code it's calling might fail -- it MUST catch all exceptions and either handle them, or re-raise them with a well defined type that is documented and declared in your API.

Anything else just leads to buggy software that has a try/catch block at the top level of the event loop/main/thread start function to deal with all the errors that leak out of its implementation and leave the process in an undefined state.

Exceptions are simply broken and awful. Java does them sorta right with checked exceptions, but the only safe thing is to not do them at all.

Re: Rust for C++ Programmers Part 7: Data Types

#20
post #14

Earlier quoted context omitted.

I tend to practice "only catch what you can handle" in exception-enabled languages - I haven't written in a systems language in almost a decade, mostly bad memories of C. How much does error-handling get in the way when you have to live without stack unwinding?

"only catch what you can handle" is incredible nonsense. Your code is the only code that knows how the code it's calling might fail -- it MUST catch all exceptions and either handle them, or re-raise them with a well defined type that is documented and declared in your API. Anything else just leads to buggy software that has a try/catch block at the top level of the event loop/main/thread start function to deal with…

Obviously you should be converting exceptions that leave your library into other areas, but internally? Converting exceptions over and over and over again just means losing information from those exceptions, or worse hiding them. If I'm forced to dump a stack-trace to the text file, I want the exact exception that caused the problem, not some vague "Operation Exception" that quintuply wraps my actual desired exception, or worse completely threw it out to "cleaned it up for me" and tells me nothing about what went wrong.

I just helped a teammate work through a bug the other day where somebody decided to "handle" a case-sensitivity problem in their home-brewed SqlLite data-access code by simply returning null for the data member if you got the wrong case. This resulted in improperly-cased column names producing objects with null members - no error happened because they were valid SQL queries, but the dictionary-reading code was silently failing when it was reading the result-set. If the program had just blown up when there was a miss on the dictionary of column names? We would've quickly found out about that stupid case-sensitivity.

Defensive coding just means your bugs go non-local and become data problems instead of exceptions.

Post reply on HN