Live data from Hacker News

Comparative unsafety

flak.tedunangst.com

91–99 of 99 posts

Re: Comparative unsafety

#91
post #64

Earlier quoted context omitted.

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

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

Re: Comparative unsafety

#92

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

Although `~0` assumes twos-complement signed numbers. Which, well, is probably a safe assumption, but still.

All modern computers use twos-complement and modern languages like Rust require it. Not assuming twos-complement is a theoretical nitpick along the lines of not assuming a byte is 8 bits. Nobody does that.

Re: Comparative unsafety

#93

Earlier quoted context omitted.

Fwiw I think it's pretty hilarious. Also I warmly recommend to view-source, the makeprogress() function is a true delight.

Abstractly it’s sort of funny as a joke against JavaScript, but it’s really not funny at all when the butt of the joke is people who have JS enabled on their browsers. It’s just elitist and obnoxious. I like jazz and think rock/pop generally sucks. If I prefaced every blog post with “people who like rock are too dumb to understand the following” I wouldn’t be making a funny joke about music. I’d just be an asshole.

Oh come on, it's a loading bar, it takes one second. It's totally not comparable to calling people dumb if they have JS enabled, like you suggest.

It'd be more like prefacing a podcast about basic music theory with a 5 second pretentious jazz tune.

Re: Comparative unsafety

#94
post #41

Earlier quoted context omitted.

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 :)

Javascript doesn't kill browsers. People kill browsers with Javascript.

Re: Comparative unsafety

#95
post #23

> I wrote some rust code. Actually, he wrote some Rust and some C code wrapped in Rust. > I was writing an smtp server... the kind of smtp server you'd write in two days. (sigh) > The error would have been detected far sooner had I not been lazy and checked the return value of chown (for a file not found error). But [excused here] You can't, and mustn't avoid C error checking when writing C code, even if it's wrapped…

> I was a TA in a first-semester course in C programming for several years, and one of the harder things to inculcate is how return value checking is not optional. It's very tempting to assume your standard library function just works, always. The situation is worse than that. People check for errors except when they don't. And you are expected to know the difference. Take for instance malloc. It can return NULL but…

> Everyone nods and say you should, and then keep writing code without checks,

Well, I would take points off for not checking return values, although TBH my basic course did not have file I/O (they would use input and output redirection instead, to keep the programs focused on more fundamental things).

> And while I'm ranting, the same goes for undefined behavior. Of course you shouldn't rely on undefined behavior. Of course of course. But maybe, just a little pointer casting is fine?

That's a trickier subject, because it's not just the hassle of checking return values, it may mean your whole approach to implementing something may go down the drain. Also, it's much less obvious; and compilers don't make an effort to warn you about it; and telling the difference between _undefined_ and _implementation-defined_ behavior is an art which few master (and I'm not even one of those few).

This is entertaining to watch regarding UB:

CppCon 2017: Piotr Padlewski “Undefined Behaviour is awesome!”

https://www.youtube.com/watch?v=ehyHyAIa5so

Re: Comparative unsafety

#96
post #35
post #23

Earlier quoted context omitted.

> I was a TA in a first-semester course in C programming for several years, and one of the harder things to inculcate is how return value checking is not optional. It's very tempting to assume your standard library function just works, always. The situation is worse than that. People check for errors except when they don't. And you are expected to know the difference. Take for instance malloc. It can return NULL but…

No one checks close()'s result because it lies. What should you write, while (close(fd) != 0) {} ? Or does it risk getting an infinite loop if, e.g., some network connection disappears? Maybe if (fsync(fd) == 0) while (close(fd) != 0) {} else abort(); But maybe fsync is interruptible too. Ultimately there is nothing strictly correct to write, and you should just not really count on files being closed before process t…

> What should you write?

You should try closing once, and if it fails, either die (e.g. with abort() ), or do something meaningful and relevant to your specific situation.

> Ultimately there is nothing strictly correct to write,

If that is the case for you - then definitely abort().

Re: Comparative unsafety

#97
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.

fsync() is not part of the standard C library. You won't be able to use it on, say, MS Windows. (Although maybe rust has it?)

Re: Comparative unsafety

#98
post #56

Earlier quoted context omitted.

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.

fsync() is not part of the standard C library. You won't be able to use it on, say, MS Windows. (Although maybe rust has it?)

Neither are open(), close(), or, indeed, file descriptors.

But they are all in Posix, which is ISO Standard 9945, based on IEEE 1003.1.

Windos has various gestures toward Posix compatibility, which might include _close() and _fsync().

Unix and Posix file system semantics are not among the best achievements of the Unix philosophy or of Posix as a universal system interface, but they have been considered "good enough" by most for a long enough time to make an actually-good replacement increasingly unlikely to take hold.

Re: Comparative unsafety

#99
post #43

Earlier quoted context omitted.

> From my perspective as someone who knows Rust but primarily works with high-level languages like JavaScript, some developers who from a background of unsafe languages (particularly C and C++) seem incredibly cavalier around `unsafe` blocks in Rust code. It's like they're desensitised to the unsafety. That's true. If you are used to 100% of your code being within the equivalent of a Rust "unsafe" block, having over…

> 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'd argue that if your unsafe usage is making your 'safe' code unsafe, then you're using unsafe wrong. The idea is that once you leave the unsafe boundary, you have guaranteed to yourself that the assumptions required by the unsafe code have been met. Maybe there needs to be a syntax to say "yes I'm really sure that out here the assumptions have been checked so this is actually safe now" just to remind developers that this is what the unsafe block is for
Post reply on HN