Hacker News new | past | comments | ask | show | jobs | submit login

I wouldn't use it as an alternative to garbage collected languages since garbage collection is just simpler overall,

I do not disagree, but I think one could argue to the contrary as well.

Since most garbage collected languages do not guarantee that objects are actually (timely) garbage collected, you cannot tie the lifetime of other resources (file descriptors, sockets, locks) to object lifetimes. So, the burden is on the programmer to ensure that resources are correctly finalized. Whereas in RAII languages you can properly tie all finalization to object lifetimes.

Note: I am not arguing that GC-ed languages cannot do RAII. AFAIR D has a GC and supports RAII.




This is true - for example, C# has the `using` block to ensure resources are deterministically released when they go out of scope, not when they at some point later are garbage collected. I would still argue C# is much simpler than Rust since for most object you don't have to worry about this. But I guess there is a certain extra complexity in C# because you actually have to know if a class owns some external resource or not, while this distinction is not necessary in Rust.


In practice that's rarely an issue, because a) if it has a resource it'll implement IDisposable and b) my IDE screams when I don't dispose an IDisposable.

This is not as good as the Rust model, but it's not awful, either.


Yes but that is just the compiler offloading some of the static analysis to the IDE. The difference might not matter if you are always in an IDE anyway, but the language would be safer if this was an actual compiler error.


In most modern languages, there is some way to tie object cleanup to a scope, even for a GC'd value. Note I say "cleanup", as in "I closed the file handle" or something, not "finalization" which is a GC-specific term. It isn't necessarily as rigorous as RAII but it works in principle in much the same way. "with" in Python, for instance, "defer" in Go (less automatic but if used properly fits the 80/20 nature of Go), Haskell has a motley crew of solutions, most other languages you can put something together yourself if nothing is provided.

Where things get complicated is when you don't have a clear scope to tie lifetime to and so you can't use these, but then, that applies to RAII too.

I'm not saying C++ doesn't have a bit of an advantage here, but I think it often gets oversold as "C++ has RAII and other languages have nothing even remotely resembling it", which isn't true.

In practice this isn't a problem that I encounter in GC'd languages anywhere near often enough to justify even a slight preference for a "true RAII" language.


It isn't necessarily as rigorous as RAII but it works in principle in much the same way.

I would say that in principle they work very differently ;).

They work superficially in the same way in that if you tie a particular object to the current scope in a RAII language, the cleanup happens at the same point as defer, with, try-with-resource, etc. would. However, there are cases where you really want cleanup to be tied to the object's lifetime.

For example, I have a Tensorflow binding for Go. However, I cannot pass Go-allocated memory (e.g. memory allocated to a slice), because Go does not allocate slice memory on 32-byte boundaries (using Go memory would cause an allocation + memcpy in Tensorflow to align the memory). So, you allocate memory in C-land and have Go structs that wrap your pointers. However, now cleanup becomes interesting. You do not want to rely on finalizers, since they are not guaranteed to run. However, using a Close method is also an annoyance. For tensors that live for the duration of a graph run, it is fine (you can use defer), but other tensors live longer and are reused between runs, shared by models, etc. It becomes unclear pretty quickly who is responsible for closing the model.

I also use a Tensorflow binding for Rust, which is drastically more convenient in this respect. Since ownership is clear, the lifetime of a tensor is bound to the scope or object that owns it. If the owner is dropped, the tensor is also dropped. If you need to share a tensor, you make an Rc/Arc the owner.


Well, to be honest, my position has consistently been that using Go for scientific programming is not a good idea, and will never be a good idea because the Go team is never going to give you the features you need for it, and that you probably should just use Rust. (Or something else. Rust is not the only choice.) The Go answer is probably that, yes, you need a .Close method, and yes, I agree that in this use case that's really annoying and I would suggest this argues against using Go for this.

People tend to then get annoyed at me for expressing this opinion, but this sort of thing is the reason why. Go is really only merely adequate at interfacing with libraries in other languages [1] which scientific programming does a lot of, and Go has a type system that seems almost precisely tuned to get in your way if you try to program mathematical code in a typed manner but at the same time isn't so weak that you can pull something like a NumPy where at the Python level everything is just untyped so as long as you assemble it correctly up there, the C level can work it all out. Nor can you practically program in that manner, because while you can slather interface{} everywhere, you can't make it convenient to work with like a dynamically-typed language. I think Go is approaching maximally pessimal for scientific-type programming, personally.

I should clarify that when I say Go has "something like" RAII, I do mean pure-Go code only. And by no means is "defer" perfect. (I'm definitely in the camp that it should have been block scoped, not function scoped, and the performance hit can be quite annoying.) It's just that, as I said, it's not like the choice is "either RAII or you're in some manual-management only horrorland"... lots of languages have block-scoped constructs (not just Go) that can be used to 80/20 RAII. That last 20 may be important in some cases, but it's quite often a great deal less important than the 80.

(This post is brought to you by your friendly local "HN poster who has been accused of being unreasonably positive about Go".)

[1] Pretty much every modern language claims to have "great" interfacing with C, despite IMHO wild variances in difficulty. Go is "adequate" because it's not too difficult to simply call a C function, and with not much labor you can get binary-level-compatible structs between the two, which is a nice advantage over Python or Perl or something. But the semantic mismatch is pretty rough around memory management and threading model, and that manifests in slowness in the calls in addition to general semantic mismatch.


Rust is better than Go for sure (cough Generics) but it's already hard enough to fight the mathematics, statistics and machine learning, scientists will not want to fight the Rust syntax as well.

For me, the most promising compiled and statically-typed language for scientific computing is Nim.

Disclaimer: I am the author of a Numpy/Torch/Tensorflow-like library written from scratch in pure Nim, the look and feel is pretty similar to Python Pytorch + Keras for neural networks: https://github.com/mratsim/Arraymancer


Well, to be honest, my position has consistently been that using Go for scientific programming is not a good idea, and will never be a good idea because the Go team is never going to give you the features you need for it, and that you probably should just use Rust. (Or something else. Rust is not the only choice.) The Go answer is probably that, yes, you need a .Close method, and yes, I agree that in this use case that's really annoying and I would suggest this argues against using Go for this.

That's a good point. Go was my camping ground while Rust was still breaking every month and I didn't want to go back to C++. Although I don't use Go anymore, I have come to appreciate its simplicity and in a lot of scenarios I would definitely recommend it.


> So, you allocate memory in C-land and have Go structs that wrap your pointers. However, now cleanup becomes interesting. You do not want to rely on finalizers, since they are not guaranteed to run.

I did similar thing with C# more than once. Not with TensorFlow, but with my only C++ libraries that also used SIMD and therefore required aligned memory buffers.

It worked just fine. There’s IDisposable for deterministic cleanup, and finalizers as a safety net. Unmanaged interop, i.e. [DllImport], is supported on all platforms, e.g. on Linux it imports from *.so libraries.


> Where things get complicated is when you don't have a clear scope to tie lifetime to and so you can't use these, but then, that applies to RAII too.

Sure, but Rust does help here specifically: resources can safely be moved into child or parent scopes.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: