17 ms·
I'm disappointed by the idea that something is semantic nonsense, but it's okay to do it anyway. The right line of code isn't "return usize::MAX", but "abort()"
by quotemstr 10y ago
I'm disappointed by the idea that something is semantic nonsense, but it's okay to do it anyway. The right line of code isn't "return usize::MAX", but "abort()". (I'd also accept "__builtin_unreachable()".)
In general, if a program enters an impossible state, you need to stop running the program, not do something you know is wrong just so the program keeps working a while longer. That's why strcpy_s is much better than strlcpy.
It's also really not fair to compare the legibility of Rust to the legibility of musl: whatever its other merits, musl is written in that old-school C style that makes one imagine that programs run faster when variable names are shorter and braces are omitted.
- CUViper 10y agoRust does have unreachable!() which panics.
- dikaiosune 10y agoPanics are possible with #![no_std], but it does require that you supply a working implementation of panic_fmt, which I haven't gotten to yet (mostly because I was waiting on panic-as-abort to land which it recently did).
- steveklabnik 10y agoNB: you still need panic_fmt with panic == abort; the default panic handler still prints a message, even though it doesn't go through with the unwinding.
- CUViper 10y agoOK, another option is to make it an explicit infinite "loop{...}" which only exits by returning. The "0.." is effectively infinite anyway, because it will overflow freely in release mode. (But surely you'll hit an invalid memory page before that happens.)
- dikaiosune 10y agoYou're right, I think this version is a bit clearer: https://is.gd/lck8c7 https://is.gd/lck8c7
- loeg 10y agopanic_fmt can be as simple as (C pseudocode): printf("..."); kill(getpid(), SIGABRT); while(true) /* In case kill(ABRT) returns, halt. */;
- dikaiosune 10y agoThis is a hobby project for my own edification, and maybe maybe to eventually show Rust's suitability for this kind of work. While I appreciate that the semantics of returning usize::MAX aren't correct, I'm also not an expert on C standard library implementations. Just a hobbyist writing a little bit about something I'm trying out. Potentially relevant is this section of the repo's README: https://github.com/dikaiosune/rusl#goals-and-non-goals https://github.com/dikaiosune/rusl#goals-and-non-goals EDIT: After a suggestion in a sibling here, here's a version which neither explicitly aborts (musl doesn't do abort or unreachable), nor uses an explicit return of usize::MAX: https://is.gd/lck8c7 https://is.gd/lck8c7 It doesn't autovectorize, but I suspect that with some work I could maybe convince LLVM to do so.
- briansmith 10y agoIMO, it would be more interesting to see it done top-down, instead of bottom-up. That is, instead of starting with rewriting `strlen`, why not start with rewriting `getaddrinfo`? That would be a very interesting project because you could write `getaddrinfo` with a safe Rust API (i.e. one that doesn't require using `unsafe` to call it), and then wrap it in an `unsafe` function that exports the unsafe C `getaddrinfo` function. This could arguably then be an improvement on the musl code.
- dikaiosune 10y agoI think it very well could be. However, I would imagine (not having looked at the problem in depth) that one would want to be able to use things like Vec<T> or CString from the Rust standard library when implementing a DNS resolver. Since the Rust standard library depends on libc, I'm not sure how one would properly allow the Rust symbols to replace the linked-in libc symbols while still depending on the rest of libc. Maybe it's not an issue? Maybe there's some magic which could be done? Nonetheless, I mostly started doing this to see if it could be done, not to prove any point (although I'd love it if a point is proven along the way), so a bottom-up approach with #![no_std] was the easiest way to prevent any sort of cyclic dependencies and/or linker issues.
- 10y ago
- nwmcsween 10y agoNo you don't abort, aborting or trapping is of last effort. A shared library aborting will kill whatever it's linked to which allows zero chance of recovering. There are two sane ways to go about handling 'impossible states': The API is designed in a way that it returns error info somehow, a type system that tracks side effects in order to allow rolling back.
- astrange 10y agoYou can't roll back real side effects like network I/O; that's what it means to be a side effect. Plus crashing (on some OSes) dumps a lot of useful process state and the current stack, but if a function just returns some error code 50 there's not even a way to find which bits of its internals returned it. If you separate things by process, you can have your crash-only recovery and keep the rest of the system going.
- dleslie 10y agoThis is why "interrupted" and "incomplete" are often valid error states.
- nwmcsween 10y agoYou can roll back most real side effects like network I/O, console output it just depends on details such as protocol and framebuffer type. You also handle errors at the point of issue, not exceptions. The issue with separating by process is you have to flush the TLB on context switches.
- deleted 10y ago[deleted]
- revelation 10y agoIt's really not fair to compare the legibility because that code is not at all doing the same. He's basically comparing his naive "one byte at a time" strlen to one at least processing at register size. The naive version looks just as good in C, frankly better, because what is this "0.." offset nonsense.
- lmm 10y ago> The naive version looks just as good in C, frankly better, because what is this "0.." offset nonsense. I'm pretty sure "for i in 0.." is clearer (to a reader unfamiliar with either) than whatever it is you'd do in C. In C it would be something like "for(int i=0;;i++)", right?
- pbsd 10y agoI'm pretty sure a simple while loop is better than either: size_t strlen(const char * s) { size_t index = 0; while(s[index] != 0) { index += 1; } return index; }
- lmm 10y agoThat's substantially harder to follow than the code in the article - you have to jump around to see where index changes and how that interacts with the loop condition.
- matthewaveryusa 10y agoJust out of curiosity how would you rank strlen{1,2,3} in terms of difficulty to read? size_t strlen1(const char * s) { const char* it =s ; while(*it != '\0') { ++it; } return it - s; } size_t ptr_distance(const void *begin, const void* end) { return end - begin; } size_t strlen2(const char * s) { const char* it =s ; while(*it != '\0') { ++it; } return ptr_distance(s,it); } int is_null(const char* p) { return *p == '\0'; } const char* find_first(const char* s, int (*predicate)(const char*)) { while(1) { if(predicate(s)) { return s; } ++s; } } size_t strlen3(const char * s) { return ptr_distance(s,find_first(s,is_null)); }
- deleted 10y ago[deleted]
- _pmf_ 10y ago> The right line of code isn't "return usize::MAX", but "abort()". No, it's not. Assertions and aborts at the library level make a library completely unusable for embedded systems. The caller checks the result for plausibility and then decides to restart or simply not to perform the guarded action.
- rkangel 10y agoThere's a difference between handling all possible error cases (however unlikely) and handling impossible cases. The moment you hit the latter case you can no longer make any assumptions about the state of the device and an abort/assert is an entirely appropriate thing to do.
- tremon 10y agoIf the caller didn't verify the plausibility of the parameters before calling the function, why is it safe to assume the caller will verify the plausibility of the function result? Especially for embedded systems, I want the system to blow up on the developer's desk, not in the field, so I'd prefer to fail early.
- nwmcsween 10y agoBecause the caller shouldn't know specifics on what is valid for the callee unless the caller has a specific and different value of valid. You don't leak information from separate functions this is how you get brittle garbage code.
- programmer_dude 10y ago> caller shouldn't know specifics on what is valid for the callee Why? How do you even use an API if you don't know what you can pass into it?
- nwmcsween 10y agoFor example strtol, you may pass whatever (implicitly nul terminated, which is bad) and it either returns a value or an error. I wasnt advocating _you_ don't know what to pass but that the caller doesn't include specifics of the callee.