Exceptions vs destructors

Discussions on more advanced topics such as monolithic vs micro-kernels, transactional memory models, and paging vs segmentation should go here. Use this forum to expand and improve the wiki!
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Exceptions vs destructors

Post by embryo2 »

This is another reply to this thread for it to be clean of unrelated stuff.
Rusky wrote:
embroy2 wrote:Object wide scope is not enough for many possible complex states.
False. Object scope is, in fact, whatever you put in an object. Representing an operation's state in a separate struct rather than scattered across the stack is useful for other reasons as well- Iterators are a prime example of this.
I see no arguments here, only some empty statements about your position. Can you elaborate why object scope is better than the exact code where the problem can emerge?
Rusky wrote:
embroy2 wrote:The Rust doesn't prevent a developer from writing empty destructor.
It doesn't need to, because library types like locks, vectors, files, etc. already have destructors which will be called regardless of whether their containing object has a destructor, or whether their containing stack frame has a finally block. The only time you need to write your own destructor is if you are wrapping manual resource management, which is rare and already requires you to know what you're doing.
So, you suggest to rely on the default destructors and think no more (because everything else is "rare")?
Rusky wrote:This is objectively better-structured because it gets rid of the out-of-band side channel which is exceptions.
Well, the word objectively is very impressive, but the arguments are missing again. Why we need some in/out channels when recovering after a problem? I prefer just to see the code, where the problem is going from, and concentrate on the exact situation the code produces. But you, it seems, prefer to concentrate on writing a very general destructors that match every possible problem in every possible situation. I just don't know how it is possible to write such very general destructors.
Rusky wrote:Exceptions, no matter what nonsense you make up about them, still have these bad properties:
  • The decision of how to handle them can be ignored, and has a bad default
  • When they are ignored, they allow resources and partially-modified state to leak when it should be encapsulated
  • When you do handle them, it's verbose and repetitive, which discourages people from handling them correctly
The only problem with exceptions is that they can be hidden by some poorly designed methods. But nobody can ignore the handling decision - everybody always decides to handle a problem or not to handle. Next, any complex state requires some serious thinking about it's recovery in case of a problem and ignoring such situation is really bad practice mostly employed by the very beginners. And finally, nothing prevents you (except some laziness) from extracting a function in case you see some repetitive code.
Rusky wrote:On the other hand, in-band error handling with "Result" still solves these exact problems:
  • The only way to get the result you want is by checking for and handling the error
  • Checking for and then explicitly ignoring an error does not leak anything out of the function, you must explicitly return an error.
  • Because they are in-band, error handling strategies can be factored out into functions and macros
  • In-band error handling is less verbose when handling errors, and more verbose when ignoring them, so it encourages people to do things correctly
I can't oppose all those statements because I have no experience of working with Rust, but my general statement is the same - it's always up to the developer and his experience if the error handling is implemented correctly or not. The minor help from the language can improve a bit development speed, but generally is far from enough for not to expect some incorrect problem handling.
Rusky wrote:In the end, the better solution is the one that defaults to making the right choice, or if there is no good default, forces the developer to make the choice. It makes common right choices short and easy to express, and uncommon/usually-bad choices harder. Exceptions do the opposite.
Checked exceptions always enforce the choice between handling them or passing them up along the calling stack, there's just no default, bad or good. And what is the "common choice" depends on the developer. If the language always enforces just one choice, then it is too inflexible and should be used with caution.
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
cmdrcoriander
Member
Member
Posts: 29
Joined: Tue Jan 20, 2015 8:33 pm

Re: Exceptions vs destructors

Post by cmdrcoriander »

embryo2 wrote:
Rusky wrote:
embroy2 wrote:The Rust doesn't prevent a developer from writing empty destructor.
It doesn't need to, because library types like locks, vectors, files, etc. already have destructors which will be called regardless of whether their containing object has a destructor, or whether their containing stack frame has a finally block. The only time you need to write your own destructor is if you are wrapping manual resource management, which is rare and already requires you to know what you're doing.
So, you suggest to rely on the default destructors and think no more (because everything else is "rare")?
Well, when "rare" means "exclusively when calling unsafe foreign code", then yeah. See http://words.steveklabnik.com/a-30-minu ... on-to-rust (the 'ownership' section).
embryo2 wrote:
Rusky wrote:This is objectively better-structured because it gets rid of the out-of-band side channel which is exceptions.
Well, the word objectively is very impressive, but the arguments are missing again. Why we need some in/out channels when recovering after a problem? I prefer just to see the code, where the problem is going from, and concentrate on the exact situation the code produces. But you, it seems, prefer to concentrate on writing a very general destructors that match every possible problem in every possible situation. I just don't know how it is possible to write such very general destructors.
You rely on the compiler to do it for you - again, see the ownership section of the document linked above.
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Re: Exceptions vs destructors

Post by embryo2 »

cmdrcoriander wrote:
embryo2 wrote:So, you suggest to rely on the default destructors and think no more (because everything else is "rare")?
Well, when "rare" means "exclusively when calling unsafe foreign code", then yeah. See http://words.steveklabnik.com/a-30-minu ... on-to-rust (the 'ownership' section).
What is the point of your message? Is it the push for reading the introduction to Rust? The ownership section doesn't shed a light on the issue of "destructors can do all you need".
cmdrcoriander wrote:
embryo2 wrote:I prefer just to see the code, where the problem is going from, and concentrate on the exact situation the code produces. But you, it seems, prefer to concentrate on writing a very general destructors that match every possible problem in every possible situation. I just don't know how it is possible to write such very general destructors.
You rely on the compiler to do it for you - again, see the ownership section of the document linked above.
Well, is the compiler able to "write very general destructors that match every possible problem in every possible situation"?
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
cmdrcoriander
Member
Member
Posts: 29
Joined: Tue Jan 20, 2015 8:33 pm

Re: Exceptions vs destructors

Post by cmdrcoriander »

embryo2 wrote:What is the point of your message? Is it the push for reading the introduction to Rust? The ownership section doesn't shed a light on the issue of "destructors can do all you need".
embryo2 wrote:Well, is the compiler able to "write very general destructors that match every possible problem in every possible situation"?
Yes, the ownership section *does* shed light on this, and that *is* the point of my message.
The Rust tutorial wrote:The Rust compiler also figures out the lifetime of i, and then inserts a corresponding free call after it’s invalid, like a destructor in C++. You get all of the benefits of manually allocated heap memory without having to do all the bookkeeping yourself. Furthermore, all of this checking is done at compile time, so there’s no runtime overhead. You’ll get (basically) the exact same code that you’d get if you wrote the correct C++, but it’s impossible to write the incorrect version, thanks to the compiler.
See also http://blog.skylight.io/rust-means-neve ... -a-socket/:
Rust Means Never Having to Close a Socket wrote:* In Rust, as in garbage collected languages, you never explicitly free memory
* In Rust, unlike in garbage collected languages, you never explicitly close or release resources like files, sockets and locks
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Re: Exceptions vs destructors

Post by embryo2 »

cmdrcoriander wrote:Yes, the ownership section *does* shed light on this, and that *is* the point of my message.
The Rust tutorial wrote:The Rust compiler also figures out the lifetime of i, and then inserts a corresponding free call after it’s invalid, like a destructor in C++. You get all of the benefits of manually allocated heap memory without having to do all the bookkeeping yourself. Furthermore, all of this checking is done at compile time, so there’s no runtime overhead. You’ll get (basically) the exact same code that you’d get if you wrote the correct C++, but it’s impossible to write the incorrect version, thanks to the compiler.
See also http://blog.skylight.io/rust-means-neve ... -a-socket/:
Rust Means Never Having to Close a Socket wrote:* In Rust, as in garbage collected languages, you never explicitly free memory
* In Rust, unlike in garbage collected languages, you never explicitly close or release resources like files, sockets and locks
All this text shows that the Rust requires a developer to allocate something, that can be used later, in heap memory instead of the stack. So, the stack memory is guarded against referencing from another function. But does Rust guard against a file system resource leak in case when files are referenced from the heap?
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
User avatar
Rusky
Member
Member
Posts: 792
Joined: Wed Jan 06, 2010 7:07 pm

Re: Exceptions vs destructors

Post by Rusky »

Yes. All resources exposed by the standard library, including files (both handle-based and memory-mapped) have destructors written for them, so no resource will ever leak. The only time developers write their own destructors is when they are also writing the code that directly does the allocation (calling malloc, fopen, etc.).
dschatz
Member
Member
Posts: 61
Joined: Wed Nov 10, 2010 10:55 pm

Re: Exceptions vs destructors

Post by dschatz »

That's not true. Rust does not prevent reference count cycles which cause leaks of any resource (memory, files, etc.)

See http://smallcultfollowing.com/babysteps ... and-leaks/
Rust gives you a lot of safety guarantees, but it doesn’t protect you from memory leaks (or deadlocks, which turns out to be a very similar problem).
User avatar
Rusky
Member
Member
Posts: 792
Joined: Wed Jan 06, 2010 7:07 pm

Re: Exceptions vs destructors

Post by Rusky »

Correct. I should have said that no resource will leak just because you forget a finally block, because destructors are run automatically. This is not as strong a guarantee as a garbage collector as far as memory goes, but it is much stronger as far as non-memory resources go.

However, reference counting cycles are already a problem you have to deal with in systems programming, in the rare case that you can't use a better allocation strategy. They're also rather tangential to the problem embryo2 is talking about- finally blocks don't solve them either.
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Re: Exceptions vs destructors

Post by embryo2 »

Rusky wrote:All resources exposed by the standard library, including files (both handle-based and memory-mapped) have destructors written for them, so no resource will ever leak. The only time developers write their own destructors is when they are also writing the code that directly does the allocation (calling malloc, fopen, etc.).
The problem is the moment when the destructor is called just missed here. How Rust determines there's no more need for a file (referenced from the heap)?
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
User avatar
Rusky
Member
Member
Posts: 792
Joined: Wed Jan 06, 2010 7:07 pm

Re: Exceptions vs destructors

Post by Rusky »

Rust uses the equivalent of C++'s unique_ptr and shared_ptr to keep track of heap references. Normally, when you allocate a "T" on the heap, you get a "Box<T>" which deallocates and calls the destructor in its own destructor. You can also use an "Rc<T>" for a thread-local reference-counted allocation, or an "Arc<T>" for a thread-safe atomically-reference-counted allocation.

There are other ways to allocate as well, such as "String," "Vec<T>," and "TypedArena<T>." They're all responsible for directly or indirectly calling the destructors of and deallocating their contents.
dschatz
Member
Member
Posts: 61
Joined: Wed Nov 10, 2010 10:55 pm

Re: Exceptions vs destructors

Post by dschatz »

The heap is a red herring here. I can just easily open a file and forget to close it whether or not the file descriptor was on the stack or the heap.

This pattern of resource management is RAII: https://en.wikipedia.org/wiki/Resource_ ... ialization. The key idea is that holding a resource is tied to object lifetime. This ensures that as long as objects are properly destroyed (which is the responsibility of the language, for the most part), then resources (as defined by libraries, user code, etc.) will be released.
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Re: Exceptions vs destructors

Post by embryo2 »

Rusky wrote:Normally, when you allocate a "T" on the heap, you get a "Box<T>" which deallocates and calls the destructor in its own destructor.
Well, I should ask again - how Rust determines that it should call Box's destructor?
Rusky wrote:There are other ways to allocate as well, such as "String," "Vec<T>," and "TypedArena<T>." They're all responsible for directly or indirectly calling the destructors of and deallocating their contents.
So, I see the problem of determining when destructor should be called is still unsolved. It means (expectedly) there's no way to write a general destructor for all cases. And consequently, it means destructors suck when compared with a clear picture of allocation and deallocation of a resource within a code, that usually fits into one screen (yes, it's about try-catch-finally).
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
embryo2
Member
Member
Posts: 397
Joined: Wed Jun 03, 2015 5:03 am

Re: Exceptions vs destructors

Post by embryo2 »

dschatz wrote:This pattern of resource management is RAII: https://en.wikipedia.org/wiki/Resource_ ... ialization. The key idea is that holding a resource is tied to object lifetime.
Under the chapter Benefits I see the following:
The advantages of RAII as a resource management technique are that it provides encapsulation, exception safety (for stack resources), and locality (it allows acquisition and release logic to be written next to each other)
It clearly tells us about only one case - when resources are allocated on the stack.

Also, I suppose even the stack allocated resources aren't safe with Rust, because reference leak through a function, called from the object's creator function, is a thing that isn't easy to prevent.
My previous account (embryo) was accidentally deleted, so I have no chance but to use something new. But may be it was a good lesson about software reliability :)
dschatz
Member
Member
Posts: 61
Joined: Wed Nov 10, 2010 10:55 pm

Re: Exceptions vs destructors

Post by dschatz »

embryo2 wrote: It clearly tells us about only one case - when resources are allocated on the stack.
This is because exception unwinding is unwinding the stack, so it will destroy any objects on the stack, it cannot destroy arbitrary objects. The idea is to bind the lifetime of a resource such as heap memory to an object (e.g. unique_ptr) which is held on a stack. That way if an exception occurs, then unwinding will cause the object on the stack to be destroyed which will in turn release the heap memory.
embryo2 wrote: Also, I suppose even the stack allocated resources aren't safe with Rust, because reference leak through a function, called from the object's creator function, is a thing that isn't easy to prevent.
That is the whole point of lifetimes in Rust (you can read about this instead of guessing how it works). Every reference passed to a function has a lifetime associated with it which must be bound by the lifetime of the object to which it refers to. The compiler statically ensures that this is so.
User avatar
Rusky
Member
Member
Posts: 792
Joined: Wed Jan 06, 2010 7:07 pm

Re: Exceptions vs destructors

Post by Rusky »

Rust's Box<T> and C++'s unique_ptr<T> are allocated on the stack. They are just wrappers around a single pointer. Their destructor deallocates the actual T object on the heap, using the pointer they contain. And in Rust (unlike C++), references cannot leak. When you take a reference to something (e.g. passing a Box<T>'s pointer to a function accepting just a reference, or even taking the address of a variable on the stack), the compiler statically ensures that it cannot live longer than the value it points to.

Because all heap objects have an owner somewhere on the stack (directly or indirectly), and because borrowed references cannot outlive the things they point to, you only ever have to write each type's destructor once. Box<T> just has one destructor that frees the heap-allocated object, and it will always be called when returning from its stack frame.
Post Reply