5 ms·
C allows much more flexibility wrt lifetimes than does rust. For example: a common pattern in video games is the frame-local allocation. You (cheaply) allocat
by moonchild 5y ago
C allows much more flexibility wrt lifetimes than does rust.
For example: a common pattern in video games is the frame-local allocation. You (cheaply) allocate some object at the beginning of a frame. Share references to it all over the place. At the end of the frame, you (cheaply) free all the objects that were allocated in this manner. This cannot really be expressed in terms of rust's ownership system.
More generally, rust enforces local correctness, while c allows for global correctness (a weaker condition). Consider, as a simpler example, this c function which I write frequently:
char *tmpstr(void) {
static char buf[16][1024];
static int i = 0;
char *ret = buf[i];
i = (i+1) % 16;
return ret;
}
This function is not locally correct; in a vacuum, it is impossible to say whether this function violates invariants. (Ignoring the problem of overflowing the 1024-byte buffer.) There are many programs using this function which are correct; then, there are some programs which call this function more than 16 times in a row and end up with undesirable behaviour as a result.
Rust requires that every function be locally correct. Papering over some details: for any sound function f and hole-containing program P[_], if P[f] is well-typed then P[f] is sound; that means that it's always meaningful to consider f without considering the context in which it will be called. There is no such guarantee in c.
As a sound language, this is a very useful attribute for rust to have, because it means that libraries can be meaningfully type-checked (not to mention it significantly simplifies the type system), but it would be naive to say that rust simply formalizes the ownership model you would have used anyway in c.
- masklinn 5y ago> C allows much more flexibility wrt lifetimes than does rust. Because C does not have lifetimes at all, or ownership as a formal langage concept, and thus does not run head first into the completeness problem. Which is one of unsafe’s use cases: when the compiler can’t prove the code is correct (because there will always be correct programs the compiler can’t prove, and there are currently a fair bit of them), and you can, and there are no suitable safe alternatives, you use unsafe. > For example: a common pattern in video games is the frame-local allocation. You (cheaply) allocate some object at the beginning of a frame. Share references to it all over the place. At the end of the frame, you (cheaply) free all the objects that were allocated in this manner. This cannot really be expressed in terms of rust's ownership system. Of course it can, though it requires APIs which are not there (in the stdlib, yet): a frame local allocator and custom per-object allocator. All frame-local objects use that and thus keep a reference to the allocator. And it avoids storing frame-local objects in locations which outlive the frame. > This function is not locally correct; in a vacuum, it is impossible to say whether this function violates invariants. That is also a use-case for unsafe: an unsafe function is one for which the caller is responsible for checking invariants. This is unsafe anyway because modifying globals is unsafe to start with.
- moonchild 5y ago>> cannot really be expressed in terms of rust's ownership system > Of course it can You cannot share objects using rust's ownership system. Interior mutability involves an unacceptable performance hit (and doesn't actually allow you to share anything). And if you use shared indices into a centralized array, then you have blinded the ownership system, creating pointers that it cannot make sense of. >> This function is not locally correct; in a vacuum, it is impossible to say whether this function violates invariants. > That is also a use-case for unsafe: an unsafe function is one for which the caller is responsible for checking invariants. That's exactly right, and was my thesis; rust's type system does not allow you to prove the correctness of such a function, because it is neither correct nor incorrect without context. 'Unsafe', as a subversion of the type system which allows unsound behaviour, is completely uninteresting wrt the topic at hand; the comparison is a purely theoretical one between non-'unsafe' rust—that is, rust's ownership model—and c.
- steveklabnik 5y agoInterior mutability does not have uniform performance characteristics, Cell has a very different cost (that is, none) over RefCell (which involves checking a flag when accessing). Etc etc. Unsafe does not allow unsound behavior, or rather, it lets you write unsound behavior but that is a bug. You still have to uphold various invariants in unsafe code. It’s there for when you know you can, but the compiler cannot, which is an inherent property of any sound system.
- moonchild 5y ago> Unsafe does not allow unsound behavior, or rather, it lets you write unsound behavior but that is a bug. You still have to uphold various invariants in unsafe code. It’s there for when you know you can, but the compiler cannot, which is an inherent property of any sound system. I don't understand what you're saying here. Rust-without-'unsafe' is a sound language; i.e. any program which types will be unable to violate runtime invariants. Rust-with-'unsafe' is an unsound language; i.e. there are programs which type but yet violate runtime invariants. That is a bug in those programs—sure, yes, of course. It is not a bug in rust; it is the language functioning as intended. However the topic at hand is the relationship between rust's type system and c's; hence unsafe is completely uninteresting, because it is a tool for subverting rust's type system. (It occurs to me now that I might have gotten a less generally defensive response had I made the original comparison between rust and a hypothetical rust without unsafe. Oh well)
- drran 5y agoYou can use your own allocator in Rust, with your own rules, OR you can use static arrays and then just reset aray length to zero at the end of frame. Your function is easy to write in (unsafe!) Rust: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cf7ca3a63d846130e4ff0602e5af34d7 https://play.rust-lang.org/?version=stable&mode=debug&editio... use lazy_static::lazy_static; // 1.4.0 use std::mem; lazy_static! { static ref BUF: [String; 16] = [ String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024), String::with_capacity(1024) ]; static ref BUF_INDEX: usize = 0; } fn tmpstr() -> &'static mut String { let index_ptr: * const usize = &*BUF_INDEX; let index: &'static mut usize = unsafe { mem::transmute(index_ptr) }; let buf_ptr: * const String = &BUF[*index]; let ret: &'static mut String = unsafe { mem::transmute(buf_ptr) }; ret.truncate(0); *index = (*index + 1usize) % BUF.len(); return ret; } fn main(){ let s1 = tmpstr(); s1.push_str("Hello, world!"); let s2 = tmpstr(); s2.push_str("And hello world again!"); dbg!(s1, s2); } BTW: It's very good idea for embedded systems.
- moonchild 5y agoYes, of course you can express this in rust. The topic at hand is whether it can be expressed in terms of rust's ownership policies. Your example shows that it cannot; it returns an object of static lifetime, avoiding the issue completely.
- littlestymaar 5y agoThe topic at hand was about the comparison between C and Rust in terms of memory model. Ownership policies are one restriction safe Rust uses to ensure that the user is adhering to its memory model, but Rust is not limited to safe Rust and you can op-out ownership policies if you use raw pointers. The difference between Rust and C in terms of memory model isn't ownership, it's “mutable xor shared”.
- moonchild 5y agoUpon further reflection, I don't think 'local/global correctness' distinction this was expressed very well or very usefully. The 'hole' example papers over so many details as to be misleading. So here's another, hopefully better expression: Rust's type system wrt lifetimes is not very expressive, compared with 'dynamically-typed' c. It can't represent things like 'the value returned by this function must remain alive for no more than 15 successive invocations of this function'.