Live data from Hacker News

Comparative unsafety

flak.tedunangst.com

81–90 of 99 posts

Re: Comparative unsafety

#81
post #12

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?

It'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.

Re: Comparative unsafety

#82
post #71
post #58

Earlier quoted context omitted.

My understanding is fsync() will tell you more than close() only when a flush of the OS cache fails to make it to disk. Is there anything else? The problem with calling fsync() is that you have to wait for it to finish. There are many scenarios where the extra data integrity guarantees you get from calling fsync() aren't important.

close() will fail to report a wide variety of failures. But Cesar is right, you can't re-try it. And fsync() can be very slow on most commonly-used file systems. We are fortunate that now SSDs are fast.

[deleted]

Re: Comparative unsafety

#83
post #77

Earlier quoted context omitted.

> Even if one in ten lines are marked as "unsafe", that's already many times less "unsafe" than what they are used to in their C or C++ code. As a Rust programmer who's written safe code and some unsafe abstractions, one in ten lines being unsafe means that the remaining 90% of your lines need to be written with the same care as your unsafe code, to avoid feeding invalid arguments into unsafe operations which cause U…

I'm baffled by why this answer is downvoted. It matches my experience precisely, and articulates the proper approach for isolating `unsafe` code so that the safe code around isn't so tricky to write .

My guess is that fewer people are proficient in writing Rust than know about it being memory-safe, and only a fraction of Rust programmers have studied how to write unsafe code, its (footgun-filled) potential for undefined behavior, and the task of writing safe interfaces that don't allow for UB... yet the rest of them vote on HN anyway, thinking that "safe code can't cause UB" (which is an incomplete mental model).

On a related note, looking at the comments of a semi-"fluff" blog post commenting on an unsafe-Rust data race (https://news.ycombinator.com/item?id=26713037), I'm the first person across all of the Mozilla bug-tracker, Mozilla blog, and HN comments to point out that Firefox had "data races in safe code" because an unsafe block in a (module-private) "safe" function was unsound, allowing other safe code in the module to cause data races. (Maybe not the first, https://news.ycombinator.com/item?id=26714209 pointed out the 'bad pattern of "sprinkle atomics it until it works"' (though not researching it as deeply as I did) and got downvoted even though they were right in this instance.)

Re: Comparative unsafety

#84

Fun fact: I hit the exact same bug when working on FamiTracker in C++, with a MFC type also called CString... "construct a CString, acquire a pointer into the internals, destroy the CString". The only reason it doesn't make FamiTracker crash is because MFC's CString is COW/refcounted, and the refcount never reached zero because it was constructed from a long-lasting string literal, so the underlying buffer was still…

I'm a big fan of this kind of improved RAII. We implemented it in https://vale.dev/blog/raii-next-steps, you might find it interesting!

Re: Comparative unsafety

#85
post #41
post #38

Earlier quoted context omitted.

It's meant as satire on the needlessly script heavy websites that many people create nowadays. The blog post itself isn't entirely serious either.

Well, unfortunately for me it resulted into an early page close. I thought someone who uses that much javascript for a page that could have been 100% static can't have anything interesting to say.

Just disable JavaScript, then the page loads instantly and works perfectly :)

Re: Comparative unsafety

#86

> Why am I using my own ffi version of chown instead of the libc crate? The libc crate prototypes chown with unsigned uid_t and gid_t types, and I want to pass -1 because I'm not interested in changing the group. I'll spare you the long rant about how one should never redeclare system interfaces if you can't take the time to do so properly, because it turns out if you dig into it, uid_t boils down to uint32_t , but e…

If your reaction to Rust actually following the prototype of chmod but not accepting conversions signed/unsigned silently is to redefine chmod so you can do it like you’d do in C… that seems a little bullheaded to me. Wrapping a C function is dangerous in a literal and real sense, doing “-1 as u32” is not at all.

> If your reaction to Rust actually following the prototype of chmod but not accepting conversions

Eh... as the article points out, the chown documentation specifically says that you are expected to provide negative numbers. The spec is obviously bugged.

This limits the amount of righteousness you can claim over "but we followed the spec (that we knew was wrong)!"

Re: Comparative unsafety

#87
post #51
post #31

> If there's an error which is a clear cut bug, I think it should be reported by an error detecting tool, not a linter Relevant rustc (merged) PR: https://github.com/rust-lang/rust/pull/75671 - "Uplift temporary-cstring-as-ptr lint from clippy into rustc"

This seems like a positive development, but there are other `as_ptr` and `as_mut_ptr` functions. The one that I tripped up with was actually from Vec, not CString. Zooming out, there are innumerable ways to create a dangling pointer. This is really a vexing problem.

Rust doesn't intend to prevent you from creating dangling pointers... if you want that functionality, use references. The reason this issue is particularly likely to hit is that it's the intersection of three things: (1) one of the very few times when people often need raw pointers when not writing very carefully inspected unsafe code is when calling functions across an FFI boundary, (2) C strings are represented by a char * pointer using a type (3) a Rust value that's created as a temporary will drop on the same line. (1) and (2) are how people can know it's almost always a bug when someone does this with a `CString`, whereas a lint would probably have a lot more false positives for something like a `Vec` (which is rarely passed to C directly since it doesn't understand it).

Keep in mind that even something like borrowing a RefCell creates a temporary, so once you cast to a pointer and end the lifetime it came from it's very hard for the type system to track back the pointer you got to any particular deallocated temporary in an intelligent way. It pretty much has to be done on a case by case basis, I suspect (but maybe that could be improved--it is definitely the case, from a study someone did recently, that a large percentage of UB in unsafe Rust is due to destructors running early unexpectedly!).

Re: Comparative unsafety

#88
post #56
post #45

Earlier quoted context omitted.

> No one checks close()'s result because it lies. And since nobody checks the result of close and very few check the result of every single write operation, many "disk full" errors go unnoticed.

If you want to know whether your writes are getting out, you will get a much more reliable indication from fsync(). So, instead of exhorting people to check close()'s result, you should exhort them to fsync() first and check that result. A very old programming principle says, "Never check for any failure you are not equipped to act on." It is sometimes used as a reminder to ensure you are always so equipped.

> A very old programming principle says, "Never check for any failure you are not equipped to act on." It is sometimes used as a reminder to ensure you are always so equipped.

This sounds like a great principle for allowing dangerous bugs to linger in a codebase for years... one can always act upon a failure by aborting.

Re: Comparative unsafety

#89
post #64

Earlier quoted context omitted.

So 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…

I 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()".

What 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.

Re: Comparative unsafety

#90

Fun fact: I hit the exact same bug when working on FamiTracker in C++, with a MFC type also called CString... "construct a CString, acquire a pointer into the internals, destroy the CString". The only reason it doesn't make FamiTracker crash is because MFC's CString is COW/refcounted, and the refcount never reached zero because it was constructed from a long-lasting string literal, so the underlying buffer was still…

I'm a big fan of this kind of improved RAII. We implemented it in https://vale.dev/blog/raii-next-steps , you might find it interesting!

Read the blog, found it somewhat interesting. Prohibiting destructors and only allowing consuming methods is certainly easier when you don't have an exception system.

Bidirectional pointers is definitely something it does better than Rust. Constraint references are an interesting idea. Clasps remind me of some Qt objects automatically detaching other objects listening to them, when destroyed (eg. QSortFilterProxyModel's source model, referenced by QSortFilterProxyModel, referenced by QAbstractItemView), but it feels ad-hoc for every individual type, and I don't know if there's underlying principles that all Qt types follow.

The descriptions of the language and others feel a bit crank-ish at times, I got lost a web of hyperlinks to various pages on the site and didn't read all of them, and apparently the language is not ready yet. I might check it out again when it's more complete.

Post reply on HN