V: Eliminate file.close() and related using destructors

Created on 27 Jul 2019  路  16Comments  路  Source: vlang/v

I thought Go missed an opportunity when garbage collection didn't extend to streams and files. They required manual "freeing/closing" instead of the language handling it for me when the variable went out of scope. Defer was pushed as a helper for calling close, but this doesn't work correctly in a loop unless it's wrapped by a function. Mistakes are often made with http clients in Go for example.

This would reduce code size, eliminate leaks and reduce cognitive load. Thoughts?

Feature Request Discussion

Most helpful comment

Perhaps this is really a feature request for destructors. How it is used would be up to the library author. Or if someone happens to create a struct wrapper for a Closer, with a destructor that calls close, that's their business. Ideally, I'd prefer if the std lib used destructors to check if sockets etc weren't closed when they dropped out of scope and auto cleaned up - but destructors would have to come first. This might challenge the need for defer admittedly.

All 16 comments

This was discussed on Telegram, several people noted that such things should be explicit.

I had a similar idea.

Just updating this. Closing can return errors. Perhaps, leave close so it can be called by those that want the feedback, but call automatically if the programmer doesn't do it.

Perhaps this is really a feature request for destructors. How it is used would be up to the library author. Or if someone happens to create a struct wrapper for a Closer, with a destructor that calls close, that's their business. Ideally, I'd prefer if the std lib used destructors to check if sockets etc weren't closed when they dropped out of scope and auto cleaned up - but destructors would have to come first. This might challenge the need for defer admittedly.

@krolaw V is very heavily influenced by Go. Like 90% Go. Other influences are felt as well but they are minor differences from Go and aid legibility. The in operator pulls from Python and is a syntactic tweak to what is more verbose in Go. This change is not diametrically opposed to how things work in Go. It's just a little different. Much easier to read in my experience and one of the few things I miss in Go from Python. Things like destructors are from languages like Java and other languages that are largely influenced by Java or C++. Go is in no way influenced by such object oriented languages. Destructors make sense for class based languages. But in a language like V, Go, Python, etc. which are more closely related to pure C in their concepts, there is little need for such functionality.

@runeimp I like V because it is heavily influenced by Go. However, this is a real problem in Go. Case in point the Go example on:
https://vlang.io/compare

You'll note that because defer is in a loop and not wrapped in a function, so the http close for every connection will not be called until the last request is being made. (I made a pull request to fix this, yet to be accepted.) This is a common error in Go and adds bugs/leaks and cognitive load. I don't mind offering the option to do it manually (as in to check for errors) if it's still handled automatically otherwise, and I'd really like a clean solution even if it isn't destructors.

V also is influenced by Rust, and Rust has destructors:
https://doc.rust-lang.org/reference/destructors.html
And uses it on files/sockets exactly in this way:
https://www.reddit.com/r/rust/comments/38ka6i/how_to_close_a_file/

I would like V to be as easy as Go (this would make it easier) and as safe as Rust. If it's not going to be as safe as Rust, V just won't be as cool.

Apologies about the emotion, I'm really passionate about this.

@krolaw I've only ever used destructors in OOP languages and haven't played much with Rust yet. So I'm familiar with destructors but never used them in a non-OOP context. Maybe Rust has a more elegant way of handling this but I'd expect that adding them to V would require modifying structs or other complex objects to have a special destructor method you would have to code yourself to ensure all the bits got cleaned up before the object itself is destroyed. That idea does not sound like a good fit for the language to me. Maybe the author of the language feels differently though.

I do agree that having to wrap a defer within an anonymous function is not ideal though. To me the solution would simply be to ensure that within the language defer declared inside a loop should trigger before the next iteration. I don't know if that would be complex to tackle in the parser or not in the case of loops within loops, etc. Anonymous functions are used extensively in Go so it's not a big deal for me. But I would rather the scope of a loop be consistent with the scope of a function. At least as far as defer goes and possibly else wise.

BTW, no need to apologies. I didn't think your statements were overly emotional. And passion is _a good thing_. As long as passion doesn't preclude an open discussion we all benefit. The world needs to be more open to others opinions. Especially when we disagree. 馃槈

@runeimp There have been a few discussions over the years in Go about limiting defer to scope. However, the consensus was there are cases where it doesn't work limiting scope and wrapping in a function is the lesser of two evils:
https://github.com/golang/go/issues/27762

To me, defer sounded amazing when it first came out, but now it feels like a poor man's destructor where too many things can go wrong (many listed involve closing):
https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa

I would argue most new programmers will be doing something with files, net or db calls and would benefit from some type of auto close. I think it's better for the complexity be with the experienced module devs than those learning to code and want to build good stuff quickly.

@runeimp:

Like 90% Go. Other influences are felt as well but they are minor differences from Go and aid legibility.

V does not have garbage collection, this forces V to have a different feature if it is to support automatic memory management well. Destructors are nothing to do with classes or OOP, they are for running code when a user-defined type goes out of scope.

(I disagree V is 90% Go - it looks like Go but it has immutability, generics and optional types which mean it has to work fundamentally differently from how Go works for slices, default values and other things).

@krolaw I think the basic problem that Go style programming would have with destructors is that it's unseen magic. Go tries to avoid problems by being explicit instead of implicit. And I expect that this is one of the tenets of Go that V tries to adhere with. As alluded to earlier I've only seen destructors used in class based OO languages. If they can be done in an explicit way so that it is obvious that is intended behavior then I think that would be acceptable. At least to me it sounds good. And @medvednikov's note above suggests that's probably not the case. I just checked out https://doc.rust-lang.org/nomicon/destructors.html and Rust doesn't seem to do anything explicitly either regarding trigger an instance to explicitly be ready to destruct itself after this point. The problem is just that it's magic. If you ignore an instance and it falls out of scope accidentally and disappears it could be very difficult to know why. This is especially true for beginners. The benefit of being explicit with something like a defer flawed as it may be is that it forces you to focus on the code where the error happens. The potential of the alternative is you write some code that uses a library and your code breaks oddly. No error from the library just suddenly something doesn't exist anymore. Then you're searching through libraries to figure out if that code has bugs to _possibly_ figure out it utilizes destructors and those are the problem in this instance. It's not just about debugging your code but getting oddly familiar with a library's internal code instead of just it's API, and having to leave the area you're actually developing in.

Mind you I loved the concept of constructors and destructors when learning class based development with ActionScript 3.0, JavaScript, PHP OO, Ruby, and Python 3 classes. When you're developing all the code it's not _too_ hard to keep track of such things. It's just part of the language's paradigm, in most cases as a necessity of the inheritance chain. But the OOP inheritance paradigm has many problems. No language is perfect. But I'm very happy to escape the OOP inheritance paradigm with languages like Go, V that strive for simplicity and explicitness that support OOP composition via embedding. To me that's closer to perfection. I used Ruby on Rails for a few years. It's awesome! Until it's not. Until you realize that while your not writing config files, etc. you are having to memorize every-single-little-thing that RoR does automagically. Because if you don't. You will be constantly surprised. But that's just me and my experiences. I'm happy to hear about experience that completely challenges me and my current concepts. 馃槂

I think if we had the option to tell an object explicitly what it's scope should be for destruction it could work. Maybe something like:

for {
    world := NewWorld()
    world :: destruct = scope

    // Do many bad things with world

    println("I fear not for the world only that it would know it's own destruction at our hands")
}

Though I would prefer

for {
    world := NewWorld()
    defer scope world.end()

    // Do many bad things with world

    println("I fear not for the world only that it would know it's own destruction at our hands")
}

Where the specifier after defer was scope, fn, etc. and the code would error if that specifier wasn't present.

@runeimp When would we want an object to destruct outside immediately going out scope and how could it be accomplished - it'll be out of scope?

What's wrong with just?

for {
    world := NewWorld()

    // Do many bad things with world

    println("I fear not for the world only that it would know it's own destruction at our hands")
       // world auto destructs here automatically - nothing to forget, leaks non-existent, safety assured
}

@runeimp Let me concede, I am totally obsessed with safety. Ideally, I'd prefer not to have to call close at all (unless I want to confirm the close was successful), but if that's not to be, I'd want the compiler to baulk if I forgot to call the close or missed a pathway where it could escape unclosed.

However, I suspect the later is more complicated for the compiler than just tracking scope and more complicated for most programmers (more code). But maybe this could be a solution that would be acceptable to us both.

Would @medvednikov have some thoughts at this point?

what about using a Python syntax, with world := NewWorld() to define clearly the scope where the object can exists?

I like with in Python. But the point of defer and not using destructors in general is about being explicit with when something like a close call should happen. Using with for processing a file and the like is great because your usually doing it all in one place. But what if we have an object that needs to be closed, paused, other random method called at a later time. Potentially after is been passed from scope to scope via channels or what have you. The with concept doesn't really support those situations. The defer concept allows for executing a specific method or function at the end of the current functions scope. For all other cases you will likely need a closure or to call the method/function at the appropriate time manually. Python uses with because it doesn't have another solution for forcing a automatic close call built into the language. The need makes sense in Python for with. Adding with to V just doesn't give us much. We have defere and the addition just gives us two ways to accomplish the same thing. Just with a new specialized syntax that is less powerful than what is already present.

Since in the V docs say the way to manually clean up arrays (which will be fixed soon) is call its free method, I presume once it's fixed the free method will automatically be called. To that end, could the free method apply to all objects, such that any object that has a free method, will have it called when it goes out of scope?

This wouldn't interfere with calling close manually, but could offer self closing if the user doesn't, if the library dev decides to offer that functionality. And it would be easy to wrap a struct around a lib object and attach a free method if it's not already supported.

Exactly what does the RRID (Ressource reclamation is destruction) in C++ : the destructors are called automatically when an object is out of object.
I think "free" shouldn't have to be called by the user, never. Just let the compiler do those things for you, it will avoid a lot of memory leaks problems

Was this page helpful?
0 / 5 - 0 ratings

Related issues

medvednikov picture medvednikov  路  3Comments

PavelVozenilek picture PavelVozenilek  路  3Comments

radare picture radare  路  3Comments

penguindark picture penguindark  路  3Comments

choleraehyq picture choleraehyq  路  3Comments