It's worth pointing out that Python's asserts can also be "compiled" away if you use the -O flag (or PYTHONOPTIMIZE=1), which eliminates their runtime cost. It's also worth pointing out that this is the reason you should *never* put side-effects or security-relevant checks in assert statements. For example, you should never do something like this: assert f.read(4) == b"\x89PNG", "Not a PNG file" # proceed to read and…
> but rather, you should do > magic = f.read(4) > assert magic == b"\x89PNG", "Not a PNG file" If the `assert` compiles out, wouldn’t -O also possibly compile the `read()` out as well given `magic` isn’t used after the assign?
Rust's Two Kinds of 'Assert' Make for Better Code
51–60 of 87 posts
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#52Earlier quoted context omitted.
How are people still making the classic `foo`/`foo_safe_version` mistake? In a supposedly modern language. And they didn't even name the safe version clearly! If the rest of Nim is designed on this level then I hope it never succeeds. Note that Rust got this exactly right. assert, debug_assert. Clear and fail-safe.
I don't understand, what's wrong with assert and doAssert? They are both enabled in release AND debug modes. You would have to explicitly compile code with -d:danger flag to disable any assertions. > And they didn't even name the safe version clearly! In this context safe version is clearly named as "release" mode, and unsafe one is even more clear - "danger" mode. "danger" obviously implies it should be used with ca…
1. There's no semantic different between `assert` and `doAssert`. Does `assert` not "do" the assert? Of course it does. The names are meant to communicate what the functions do, and these fail. It should be called `assert` and `always_assert` or something....
2. Except that it shouldn't because the "obvious" one to use (`assert`) should be the safest. People don't always read the manual (including you) and they sometimes make mistakes (yes, including you), so the default actions should be safe. Danger should be opt-in, not opt-out-if-you-happen-to-know-you-have-to.
That's why it's `assert`/`debug_assert` not `release_assert`/`assert`.
There are a couple of famous examples where they got it completely wrong: Python's YAML library has `load` and `load_safe`. MySQL has `escape` and `real_escape`. There's probably more.
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#53Earlier quoted context omitted.
How are people still making the classic `foo`/`foo_safe_version` mistake? In a supposedly modern language. And they didn't even name the safe version clearly! If the rest of Nim is designed on this level then I hope it never succeeds. Note that Rust got this exactly right. assert, debug_assert. Clear and fail-safe.
I don't understand, what's wrong with assert and doAssert? They are both enabled in release AND debug modes. You would have to explicitly compile code with -d:danger flag to disable any assertions. > And they didn't even name the safe version clearly! In this context safe version is clearly named as "release" mode, and unsafe one is even more clear - "danger" mode. "danger" obviously implies it should be used with ca…
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#54I remember someone arguing that disabling assertions in prod is like wearing a life jacket in the harbor but throwing it overboard going to sea. And Moore's Law paid for them years ago.
> It is on production runs that the security is most required, since it is the results of production runs that will actually be trusted as the basis of actions such as expenditure of money and perhaps even lives. The strategy now recommended to many programmers is equivalent to that of a sailor who wears a lifejacket during his training on dry land but takes it off when he is sailing his boat on the sea. It is small wonder that computers acquire a bad reputation when programmed in accordance with this common policy.
It is also quoted by Donald Knuth in "Structured programming with goto statements" (1974) [1] ( which incidentally is also the source of the quote about premature optimization):
> He [Tony Hoare] points out quite correctly that the current practice of compiling subscript range checks into the machine code while a program is being tested, then suppressing the check during production runs, is like a sailor who wears his life preserver while training on land but leaves it behind when he sails!
[0]: https://ora.ox.ac.uk/objects/uuid:dff9483b-e72f-4599-bf90-76... p. 341
[1]: https://dl.acm.org/doi/pdf/10.1145/356635.356640 p. 269
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#55Earlier quoted context omitted.
I don't understand, what's wrong with assert and doAssert? They are both enabled in release AND debug modes. You would have to explicitly compile code with -d:danger flag to disable any assertions. > And they didn't even name the safe version clearly! In this context safe version is clearly named as "release" mode, and unsafe one is even more clear - "danger" mode. "danger" obviously implies it should be used with ca…
Ok I guess we've all got to learn it at some point. Maybe there should be some kind of test of lessons that "the industry" has learnt for new people. Anyway... 1. There's no semantic different between `assert` and `doAssert`. Does `assert` not "do" the assert? Of course it does. The names are meant to communicate what the functions do, and these fail. It should be called `assert` and `always_assert` or something....…
There is a difference, but I agree that it's very subtle. In english do before a verb adds an emphasis so it's like "assert assert" or "really assert" put in a short easy to type form.
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#56Earlier quoted context omitted.
In terms of category theory, what we would need is subtraction and division types on top of product and sum types. So a 32-bit integer is the product of 32 two-state bit types. Something akin to NonZero could be defined as that type minus one state, such that there are now 4294967296 - 1 representable values. Similarly, pointer types on some machines always have some bits set to 0 due to hardware constraints. These c…
As far as I understand it there's no need for that. You don't need to say [0, 256) except 0; you can just say [1, 256).
But then, its kinda hard to optimise such a split range in the general case. The narrowing option gives you 80% (maybe more) of the performance at 20% (probably even less) of the cost.
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#57Earlier quoted context omitted.
In general, you need a type system that supports sets of integer values, e.g. range(2, 7) or set(3, 5, 7). Rust doesn't support that unfortunately so it has a special annotation instead to make NonZero work.
In terms of category theory, what we would need is subtraction and division types on top of product and sum types. So a 32-bit integer is the product of 32 two-state bit types. Something akin to NonZero could be defined as that type minus one state, such that there are now 4294967296 - 1 representable values. Similarly, pointer types on some machines always have some bits set to 0 due to hardware constraints. These c…
I can see a couple of problem with this approach:
- you need to be able to talk about bit-level types, which contradicts the common assumption that all types are addressable, i.e. whose size and alignment is some positive integer (or zero) number of bytes;
- what if you want to substract more complex set of values? For example describing a 32 bit number without all multiples of 7? And how do you encode this in the compiler in such a way that type checking remains decidable?
- how do you safely and concisely express conversions and operations on these kind of types?
> Similarly, pointer types on some machines always have some bits set to 0 due to hardware constraints. These can be represented in the type system as 2^64 / 2^n where 'n' is the number of bits that are not usable, resulting in something like 2^46 for typical CPUs. This would allow extra bits of state to be "packed in there".
Note that this is not forward compatible, since newer CPUs can start using more bits. In fact recent CPUs started using 57 bits instead of 48 bits, so a program that packed more state in those top bits would now be broken. You should generally instead try to pack data in the least significant bits that will always be 0 due to alignment constraints.
Moreover the top bits are not always 0, they are equal to the most relevant bit of the used part of the address. On Linux this just happens to always be 0 for userspace addresses (and 1 for kernel addresses) but this won't be the case on all architectures and OSes.
I also wonder how you would define these types using subtraction/division types such that they are different?
- the address type being 64 bits but using only the least significant 48 bits and having the top 16 bits always 0 - the address type being 64 bits but using only the least significant 48 bits and having the top 16 bits always equal to the 48th bit - the address type being 48 bits
Clearly these types are all isomorphic, but they are definitionally different and in a way that really matters to the hardware.
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#58What about the fact that the assert causes a panic that crashes the program? Is there not a better alternative of doing your bounds checking as a first class aspect of the function and returning an error? If someone is deep into a session with your application and the cursor somehow drifts into an unknown state (which could be corrected with the “home” key or something else), for example, getting too crazy with asser…
Your example is an incorrect use of an assert. You never use assert for conditions that are logical (error) conditions that the program is expected to handle. For example your browser handling 404 HTML error is only an error from the user perspective. From the software correctness perspective there's no error, there's just a logical condition that needs to be reasoned about. Compare this to a BUG which is a mistake (…
Perhaps I should have been clearer that it was a programming error that allowed the cursor to get to a state where it wasn’t visible.
Re: Rust's Two Kinds of 'Assert' Make for Better Code
#59Re: Rust's Two Kinds of 'Assert' Make for Better Code
#60Earlier quoted context omitted.
> but rather, you should do > magic = f.read(4) > assert magic == b"\x89PNG", "Not a PNG file" If the `assert` compiles out, wouldn’t -O also possibly compile the `read()` out as well given `magic` isn’t used after the assign?
No. Ignoring that Python does not have anywhere near this level of optimisation, read() has side effects so optimising it away would be broken in the general case. It could be optimised away if all following uses would invalidate (seek, but only with SEEK_SET or SEEK_END) or ignore (pread/pwrite) the file offset, but that seems like an enormous amount of fussy work for what I would guess is little to no payback.