5 ms·
I assume this is possible in Rust, so someone please help me understand: why not just print the stack trace? In my opinion, if you suffer a sufficiently fatal
by chaorace 6y ago
I assume this is possible in Rust, so someone please help me understand: why not just print the stack trace?
In my opinion, if you suffer a sufficiently fatal error, you should just crash outright and log the trace + error message. Barring that luxury, you should log the trace + error message, then attempt recovery.
That seems to be what unwrap here would do on its own, so why would that be bad practice (assuming the error was fatal in the first place?). Am I just misunderstanding something fundamental?
- timw4mail 6y agoNot every error is fatal, and unwrap can make trivial errors fatal.
- chaorace 6y agoI'm willing to accept that argument for unwrap, makes sense if you're dealing with a situation where multiple errors can apply. Outside of unwrap, which actually crashes out, is it not possible to access the stack trace? Forgive my ignorance, I've never worked in Rust nor any other on-the-metal languages before!
- timw4mail 6y agoIn Rust there generally isn't a stack trace, because you generally can know where the error comes from. What you get for an error depends on what is giving you the error. Generally you get an enum with the general error type, and maybe a way to access the specific error.
- chaorace 6y ago> In Rust there generally isn't a stack trace, because you generally can know where the error comes from. I don't understand. Does that mean that there's sometimes a stack trace? Why would it matter if you had other ways to know where the error comes from? Surely you could say the same thing for the majority of programming languages. Sure, Rust can wrap errors via the type system, but I don't think that's a compiler-level guarantee. Maybe it's just a language design decision, then? > Generally you get an enum with the general error type, and maybe a way to access the specific error. That seems odd to me! Is the presence of a pointer to an error object something that's implementation-specific? If so, is it not typically done in the standard library? As far as you can speculate, what's the design justification for that?
- timw4mail 6y agoError traces are generally only on panic. Rust does not have exceptions, and errors must generally be handled where they can occur. Error context is implementation-specific. This is partially because Rust is a young language, and the best-practice API hasn't really been figured out yet.
- steveklabnik 6y ago> Does that mean that there's sometimes a stack trace? Yes. There's a bunch of different bits that come together to cause this, but the core of it is roughly that keeping stack traces adds a cost that not everyone is willing to pay at all times. Rust is trying to figure out how to best accomplish good error handling in an environment that doesn't provide easy answers like "just do it like x at all times." > Is the presence of a pointer to an error object something that's implementation-specific? To use the Go parlance, errors are values in Rust. They're not inherently special. There is an interface you can implement if you want interoperability, but Rust only gives you the ability to interoperate, it cannot force you to.
- SuperCuber 6y agoI think the other replies are a bit confusing. unwrap causes a "panic" - panics in rust are similar to python exceptions, but it's not catchable. (there actually is a way to catch panics because that's how the stacktrace printing is implemented, but you usually only use it if you're making a custom allocator or runtime or some other voodoo magic) So if you want your "exception" to be able to be handled by the caller, you have to indicate it otherwise, usually with enums.
- q3k 6y agoStack traces are not the best context for operational debugging. a) stack traces generally don't capture parameters b) stack tracers can be totally desynchronized with intent flow c) stack traces expose unnecessary internal noise Compare: Traceback (most recent call last): File "/var/prod/deadbeef/models/sql/peers.py", line 1337, in tx db.execute() File "/var/prod/deadbeef/models/sql/session.py", line 1337, in session tx(peers.add(a, b)) File "/var/prod/deadbeef/models/__init__.py", line 1337, in AddBGPPeer session(peers, AddPeers(a, b)) File "/var/prod/deadbeef/views.py", line 1337, in _view_add_peer AddBGPPeer(user, other) File "/var/prod/deadbeef/web.py", line 1337, in handle _view_add_peer(ctx) Exception: DB connector returned: unique constraint failed to: Failed to add peer: models.AddBGPPeer(204480, 204480): insert peer: tx aborted: unique constraint failed The first requires further logging and code reading to understand what might've caused the error. The latter immediately gives you information you need to understand what happened (the validation layer didn't catch an invalid self-reference, causing a database UNIQUE constraint to fail, and the database code caller didn't gracefully translate this error either). I use the following pattern in Go very often: foo, err := m.AddBGPPeer(a, b, secret, list) if err != nil { pretty := list.StringSeparated(", ") return nil, fmt.Errorf("AddBGPPeer(%q, %q, _, %s): %w", a, b, pretty, err) } The fact that Go error handling is just bog standard Go code allows for some quite complex generation of error messages/hints/contexts that couldn't be caught automatically from a stacktrace, or would have some awkward syntax/semantics with more complex syntactic sugar.
- diggan 6y agoIn a larger codebase, a combination of both would be more helpful. With the last one, you're not getting the same view as with the latter, and in a large codebase where the error could be triggered from many directions, it would be better to have the first example, at least in my experience. Traceback (most recent call last): File "/var/prod/deadbeef/models/sql/peers.py", line 1337, in tx db.execute() File "/var/prod/deadbeef/models/sql/session.py", line 1337, in session tx(peers.add(a, b)) File "/var/prod/deadbeef/models/__init__.py", line 1337, in AddBGPPeer session(peers, AddPeers(a, b)) File "/var/prod/deadbeef/views.py", line 1337, in _view_add_peer AddBGPPeer(user, other) File "/var/prod/deadbeef/web.py", line 1337, in handle _view_add_peer(ctx) Exception: Failed to add peer: models.AddBGPPeer(204480, 204480): insert peer: tx aborted: unique constraint failed