6 ms·
Thanks for the detailed answer! I have more questions, if you'll indulge me: If I understood it correctly, you think smart pointers and reference counting are
by maleldil 5y ago
Thanks for the detailed answer! I have more questions, if you'll indulge me:
If I understood it correctly, you think smart pointers and reference counting are bad habits. Why? Especially the smart pointers bit.
Why does Zig use less memory than other languages? Is it inherent to Zig, or can it be reproduced in other languages?
- lupire 5y agoI think Zig prefers explicit memory management, because allocations may fail and should be handled explicitly, and because automatic deallocations lead to hard-to-predict lifetimes (excess memory usage, and bugs for resource handles that are destructed at hard to predict moments). These are things that a "systems language" programmer should put in the work to do correctly/near-optimally, and not ask the compiler to just do something "good enough", like Python would.
- coder543 5y ago> because automatic deallocations lead to hard-to-predict lifetimes (excess memory usage, and bugs for resource handles that are destructed at hard to predict moments). I don't really feel this is the case in Rust.
- throwawayzr 5y ago
- varajelle 5y agoBut it is. If you have a String on the stack, it's memory is only reclaimed at the end of the scope, while it often could be free'd before. This is especially bad in async code around await point since it means the memory need to be kept alive more than needed.
- coder543 5y agoI disagree. It is substantially and unequivocally better to hold onto memory until the end of scope than to leak memory by default. How can anyone argue that leaking memory is a better default? That’s a ticking time bomb. Maybe someone is so optimistic as to believe they’ll catch every leak before shipping new code? You can easily add a manual “drop” call in Rust at any point if you want to force an allocation to be freed sooner, but I speak from years of experience using Rust for work when I say that Rust’s RAII model is not problematic in practice. I’m not simply speculating or theorizing, and I have professional experience with a variety of languages at all levels of the stack. I personally don’t mind garbage collectors most of the time, but Rust is great when you need more control. In C++, RAII can absolutely be problematic because you are able to easily do things that cause undefined behavior by accident, which is arguably worse than either leaking by default or the mere act of holding onto memory for the duration of a scope. If you can propose a system which cannot be contrived to have any downside, that would be fantastic! In the real world, Rust’s approach to memory management is extremely pragmatic and beneficial. I’m sure someone will eventually improve on Rust’s approach, but “leak by default” isn’t it. I honestly do enjoy following Zig… it is a fascinating language taking a really interesting approach to solving many problems, but its memory safety story is not where I want it to be yet. Leaking memory by default is technically safe, but it's not exactly endearing.
- varajelle 5y agoOf course you can call drop() manually, but almost nobody does or even think about it because that's not the way you program in a language with RAII. Don't get me wrong. I do think that rust and c++ RAII is much more convenient and safe than the C or Zig way. (I'd even prefer if you could annotate given struct in rust so the compiler could drop them as soon as it's no longer used, but that s not that simple)
- coder543 5y agoI definitely wish that Non-Lexical Lifetimes would eagerly drop anything that doesn't implement Drop. It would probably be a breaking change to automatically call an explicit Drop implementation anywhere other than the end of the current scope, so I think that would have to be left as-is. String doesn't implement Drop, so it could easily be dropped eagerly within the scope as soon as it won't be referenced again. Such a change would be roughly equivalent to any of the compiler optimizations that reorder statements in ways that should be unobservable.
- _3u10 5y agoStacks are a fixed size. Unless you are running out of stack space there is no point in “freeing” memory from the stack. Where in the stack the stack pointer points has no bearing on stack size for any OS I’m aware of. I’m assuming by string you mean a stack allocated array of char and not a std::string.
- varajelle 5y agoI meant a std::string on the stack, which is just a handle for more memory on the heap
- pcwalton 5y agoThis doesn't match the experience of GC'd languages. I've never heard of problems in practice that arose from treating all values present on the stack/registers as GC roots, even if those values are dead in the data flow sense.
- kaba0 5y agoYou can just put a single pair of bracket around it and that’s it. For locks it is seriously the best way to manage them.
- charcircuit 5y agoThe lifetime of the memory a smart pointer manages is very predictable. It will have the same lifetime as the smart pointer itself (this is ignoring moves).
- RustyConsul 5y agoI think what he's saying is there is a way to carelessly use smart pointers in rust. pub enum List { Empty, Elem(i32, Box<List>), } instead of : pub struct List { head: Link, } enum Link { Empty, Some(Box<Node>), } struct Node { elem: i32, next: Link, }
- charcircuit 5y agoCan you explain the difference between these approaches? Is it just that the first example allocates an extra u16 (tag of the tagged union) (ignoring any overhead)?
- tialaramex 5y agoNotice that if I make this alleged "List" with a single data item in it, my data lives in the List object I just made (probably on the stack), but an empty List gets allocated on the heap. I thought Aria's "Entirely Too Many Lists" tutorial actually tries to build this, but it actually doesn't, she draws you the resulting "list" and then is like, OK, that's clearly not what we want, let's build an actual (bad, slow, wasteful) linked list as our first attempt.
- kaba0 5y agoI don’t really get your example, or how does it have to do anything with rust? It’s just a bad linked list, isn’t it?
- tialaramex 5y agoAre you talking about the example by RustyConsul? Because I'm not RustyConsul so I can't tell you what specifically RustyConsul intended with this example. Yes this is a bad data structure in Rust, or C, or presumably Zig. In the context of this thread I'm sure Zig's proponents will say that you'd never make this mistake in Zig.
- pjmlp 5y agoAnd as we know from C, every developer is quite capable of taking care of use after free possible bugs.
- messe 5y agoThe general purpose allocator in the zig standard library has protections against use after free bugs.
- pjmlp 5y agoJust like C and C++ debugging allocators, so what is the improvement here?
- pcwalton 5y agoBy quarantining all memory forever. This is not a scalable solution because keeping one allocation in a 4kB page alive will leak the whole rest of the page. And if you don't quarantine all memory forever then use after free comes back. If it were that easy to solve UAF then C++ would have solved it by now. There is a scalable solution for UAF that doesn't involve introducing a lifetime/region system: garbage collection. Of course, that comes with its own set of tradeoffs.
- AndyKelley 5y agoThe problem of a single allocation keeping a 4 KB page alive is something that you might commonly find with the C++ or Rust way of programming that encourages allocating many individual objects on the heap, but in Zig land this is pretty rare. In fact, compare a Zig implementation of a given application with an equivalent in any other language and you will find there is no contest with respect to the size of the memory footprint. There are many cool possibilities that C++ has never explored and frankly I find your argument unimaginative.
- pcwalton 5y agoAn allocator that can leak (4kB - epsilon) of memory for each allocation is broken. It's not a question of whether the language makes such allocations unusual: free() not actually allowing memory to be freed is a violation of the contract of a memory allocator. From the man page (which I believe quotes ISO C): "The free() function shall cause the space pointed to by ptr to be deallocated; that is, made available for further allocation." If free doesn't do that because another allocation is pointing into that page, that's a violation of the contract. Standard quarantine doesn't violate the spec because the space will eventually become available no matter what, but perpetual quarantine does. One way to fix the problem would be to round up all allocations to 4kB, but that's very wasteful and slow due to cache and page table traffic; you'd be better off from a performance point of view with a garbage collector. More promising is ARM MTE, though the tag is currently too small to really be called a solution to UAF as opposed to a mitigation. A 64-bit tag would be enough, but I'm not sure what the performance costs of that would be--I wouldn't be surprised if the increased memory traffic makes it slower than a GC.