Idiomatic error handling in `new()` and builder pattern?

The point being made is that while drop glue is being utilized, actually implementing Drop is relatively rare; instead, you just rely on the drop glue dropping all fields automatically.

The point where this matters is clarifying that you don't write the Go-style let file = open(); defer { close(file); }; you just write let file = open();, and the RAII close is automatically run at scope end as part of the type.

Describing this as "not using Drop" is perhaps misleading but not strictly incorrect.

6 Likes

I can concede that "use" was not an optimal choice of word. But this is still oddly nitpicky.

For instance, no one would bat an eye at someone saying, "I use a hammer". It's irregular to call attention to the irrelevant by saying something like "I use air" because you breathe all day long. Such as it is with destructors in Rust, they just happen, and you don't have to think about it.

2 Likes

I would say that “I use air” would be perfectly fine to say in a discussion with undersea dweller.

And you have to keep in mind that most developers out there are “undersea dwellers” when it comes to Rust, Drop and RAII.

Okay, I'll give you: C++ers are amphibians, they actually invented RAII before Rust started using it to move from water's edge.

The suggestion about try_new is a winner for me. I didn't know about that pattern, and it's a good fit, other examples return Result just like I want to.

Regarding Drop trait vs scopeguard::defer, there's a place for each in my book.
One place where defer seems better is when I have multiple resources in play and (worst/usual case) are associated with external resources (outside my process: a system device, or network session...). Then I want to write the cleanup code myself, so I can control the order of clean up operations (most critical isn't necessarily top of stack) and persistence (so one cleanup panicing doesn't necessarily prevent the next from being attempted).
But not every application is critical, and Drop gracefully handles many, many scenarios.

Not trying to convince you of something, just dropping a couple of thoughts:

This can be controlled with Drop by simply combining everything in a struct - struct fields are always dropped in order of their declaration (that's documented and guaranteed).

It generally doesn't with Drop, too. The only case I can think of is panic-while-panicking, i.e. if the value is dropped because of unwind and Drop panics itself, which leads to immediate abort, but I'm not sure how it could be mitigated with scopeguard::defer, either.

4 Likes

But… scopeguard::defer() is Drop. At least I don't know of any parallel mechanism for automatic cleanup by which it could possibly be implemented.

2 Likes

A small asterisk on this: when dropping not as part of a panic-unwind causes a panic-unwind, this (currently) will propagate the unwind outwards, but whether things "near" the thing which panicked while dropping may or may not be dropped, and whether they get fully dropped can be somewhat inconsistent[1].

As an example, if your struct Thingy implements Drop and panics from that implementation, IIRC the members of Thingy will not currently be dropped; they'll just be forgotten.

This can be a huge footgun; in general, if you're writing code where types' destructors are required to run for safety (you're utilizing pinning), try to be absolutely sure that your destructors don't unwind.

In 99.9% of cases, though, it doesn't matter. The only crater-visible code which deliberately panics from Drop::drop does so for tests or to emulate linear types, and both shouldn't panic outside development builds.


  1. If you're aware of the discussion around potentially making an attempt to unwind out of Drop always abort, this (apparent) inconsistency is actually a significant motivating factor for doing so. ↩︎

Seems like they are.

2 Likes

The only real case where panicking from Drop can fail to fully drop surrounding objects is when multiple objects are in a user-defined container that manually tracks ownership, one element panics on drop, and the container doesn't implement handling to drop the remaining elements. The built-in drop glue for structs, tuples, slices, etc. has no issues with dropping remaining elements following a panic. And almost all of the standard containers are careful to either use a drop guard to keep dropping the remaining elements, delegate to another container, or delegate to the drop glue for slices.[1] So perhaps the correct advice might be, "Don't automatically rely on external user-defined code to fully drop your drop-sensitive types if you unwind through it."


  1. Except for the types from hashbrown, and the new std::sync::mpmc channels. Those should probably be fixed. Also, Rc, Arc, and ThinBox leak the allocation if dropping the item panics; not sure if that's intentional. ↩︎

1 Like

I'm being nagged to nominate a solution to my question, so I've tagged the answer suggesting the pattern 'try_new' for fallible constructors, but honors should also go to the clarification that ? can appear after any function call in a chain, not just right before a semicolon.

Thanks all for your insights!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.