5 ms·
Wait, what? I don't know rust, at all, but why is path freed? I thought rust didn't have GC? > let path = CString::new(filename.as_str()).unwrap().as_ptr(); I
by howeyc 6y ago
Wait, what? I don't know rust, at all, but why is path freed? I thought rust didn't have GC?
> let path = CString::new(filename.as_str()).unwrap().as_ptr();
Is one of those functions calling free behind the scenes?
- deleted 6y ago[deleted]
- OskarS 6y agoI mean, yeah, at the end you only get a raw pointer, the object managing the pointer doesn't exist anymore. I'm not a Rust programmer, but I would imagine it's equivalent to the following C++ code auto ptr = std::string(something).c_str(); This creates a std::string, calls c_str() on it to get the pointer. However, since the std::string isn't stored anywhere, its lifetime stops and it is destructed (this is why these are called temporaries), and the pointer is now invalid. The right way of doing it is this: std::string str { something }; auto ptr = str.c_str(); Now, str and ptr both live in the same scope, and ptr's lifetime is entirely contained in str's.
- remexre 6y agolike C++, rust frees values once they've gone out of scope; the result of CString::new(filename.as_str()).unwrap() is a temporary which is freed at the end of that line, since the .as_ptr() returns a non-lifetime-encumbered value.
- cesarb 6y agoIt's freed for the same reason it would be freed in C++. Like C++, Rust relies heavily on RAII, in which resources are freed once they get out of scope. The "CString::new(filename.as_str()).unwrap()" returns a CString, which owns the memory for the C string. Since it's a temporary (it's not being stored anywhere), its scope ends before the next line, so the resources it owns are freed (by calling its Drop implementation) just after the ".as_ptr()" call. One solution would be to split it into two lines: let path = CString::new(filename.as_str()).unwrap(); let path = path.as_ptr(); That way, the CString would only release its resources at the end of the block. With references, the borrow checker doesn't let you do it the wrong way; however, the borrow checker only applies to references, not to raw pointers (which is what .as_ptr() returns).
- iainmerrick 6y agoSo shouldn’t .as_ptr() itself be marked unsafe? That way only the correct version of this code, with the .as_ptr() inside the unsafe block, would compile, right? (I assume there’s a good reason why this isn’t the case, but I don’t know Rust well enough to know the answer. I’d be interested to learn, if anyone can explain it for me!) Edit to clarify: what seems very strange to me is that a function that not only does something potentially unsafe, but returns an unsafe value would be callable outside of an “unsafe” block.
- yakubin 6y agoI don't see anything unsafe here. Merely storing a pointer, whatever it may be, doesn't violate memory-safety. Dereferencing a pointer in some cases does. The article contains one of those cases: use-after-free. If the pointer wasn't dereferenced, all code would be fine, including the line with ".as_ptr()".
- iainmerrick 6y agoWhat else do you do with a pointer, other than eventually dereference it? It points to invalid memory as soon as the underlying object is freed. It’s hard to think of a legitimate usage at that point. You’d hope that’s exactly the kind of thing the borrow checker would be able to warn you about. One situation where you might conceivably want to do something with that dangling pointer is if you were writing an allocator. But in this particular example, we’re not writing an allocator; and if we were, we wouldn’t call .as_ptr() on one of the allocated objects — that would be working at the wrong level of abstraction. If you really do just need to store the value and not dereference it, shouldn’t it be stored as some integer type? Then to dereference it, you’d first need to cast it to a pointer, and that operation could be marked unsafe.
- yakubin 6y ago> What else do you do with a pointer, other than eventually dereference it? I could check whether two objects were stored at the same location (like comparing id-s in Python). Or I could write a crappy random number generator based on the pointers that I get from heap allocations. Or I could inspect the memory layout of a struct based on member offsets (pointer::offset_from method - perfectly safe). Pointers whose pointees are tracked by the compiler are called "references". If you fetch a pointer instead, then you opt-out of this tracking. > One situation where you might conceivably want to do something with that dangling pointer is if you were writing an allocator. But in this particular example, we’re not writing an allocator; and if we were, we wouldn’t call .as_ptr() on one of the allocated objects — that would be working at the wrong level of abstraction. I can see someone writing an allocator based on std::vec::Vec, and then ".as_ptr()" is useful. > If you really do just need to store the value and not dereference it, shouldn’t it be stored as some integer type? Then to dereference it, you’d first need to cast it to a pointer, and that operation could be marked unsafe. You can't dereference a pointer in Rust per se. You need to convert it to a reference and then use the reference. This conversion is marked unsafe in Rust. There are also some "read" methods defined on pointers - also marked unsafe. But in the example in the article the pointer was dereferenced by C code, so Rust can't do anything about it. Now imagine how an ".as_ptr()" which prevents taking pointers to temporary objects would need to be implemented. Well, you'd need to be able to write a method in Rust, which couldn't be called on temporaries. So the type-system would need to be able to express something like fn as_ptr(&'non-temporary self) -> *Self But then you'd be able to write this just fine: let ptr; { let path = CString::new(filename.as_str()).unwrap(); ptr = path.as_ptr(); } Because path is not a temporary in the above code. The issue is ptr outlives the lifetime of path. So if the type-system had a special facility for avoiding pointers to temporaries, it would be trivially circumventable by creating a variable. Doesn't sound very human-friendly and doesn't sound like a big gain. This complexity doesn't pay for itself. We really need lifetimes (which we already have for references) for that. The solutions here are: 1. Borrow checker for objects with predictable lifetimes. 2. Smart pointers for objects with hard-to-predict lifetimes. 3. For calling C code, being careful, because it's C code and we can't fix C. Also, a linter with checks for common errors like those.
- mqus 6y agoto be clear, not path is freed, but what path points to, meaning the result of CString::new(...).unwrap() Because it is not bound to a variable, it will get dropped before the next line, even if we create a pointer to it. if you would have borrowed instead of creating a pointer this would have been a compiler error because the borrow would have outlived the original object. But a pointer exists outside of the borrow semantics and lifetimes, that is also why it can only be used inside the unsafe block.
- cryptonector 6y agoIt's freed for the same reason it could be freed in Java. In Java it could be freed immediately after as_ptr() returns if that was the last reference, but may be freed later. In Rust it would be freed immediately after as_ptr() returns because that was the last reference.