HN user

ssokolow

198 karma
Posts0
Comments135
View on HN
No posts found.

*nod* As-is, we're stuck with hacks like custom shortcodes and emoji.

...though, given the inconsistent naming of consistently laid-out buttons, I think anything that makes its way into Unicode should include something that follows the lead of what Batocera Linux does on their Wiki and with custom emojis in their Discord.

See https://wiki.batocera.org/configure_a_controller for an example of how they look inline but the gist is that it's an outline of the SNES-originated diamond of action buttons that pretty much everyone but Nintendo uses these days and which is embodied in XInput and the SDL Gamepad API, with one of the circles filled in to represent the button in question.

By "game tutorials", I think they mean modern successors to the role GameFAQs used to play.

There is a combining character that, by its description, sounds like it should be implemented to do the desired thing (U+20DD Combining Enclosing Circle), but my fonts don't render it very well when I stuff geometric characters matching the PlayStation buttons into it.

Without spaces: △⃝□⃝×⃝○⃝

With two spaces between each one so you can see how "enclosing" is getting interpreted: △⃝ □⃝ ×⃝ ○⃝

For the Markdown renderer I'm working on to replace WordPress for my blog, I resorted to shortcodes which resolve to CSS styling the `<kbd>` tag with `title` attributes to clarify and the occasional bit of inline SVG for things where I didn't want to specify a fixed font to get sufficient consistency, like PlayStation button glyphs.

https://imgur.com/a/1EPm7QV

(In all fairness, it's a nerd-snipe made based on the idea that I'll be more willing to blog about things I have nice tools for. I don't currently typeset button presses in any form.)

I know Sylvain Kerkour is a perennial "Rust should be more like Go. I don't care that they're trying to meet different needs" person and has been for many years now, but I do wish we could at least get a little acknowledgement that Rust's design took a great deal of influence from Python, both on what worked and what didn't, and that this was a direct response to how, as Amber Brown put it, Python has batteries included, but they're leaking.

Python is the most infamous example of how putting something in the standard library doesn't automatically mean everyone will use it.

For example, as of the end of the Python 2.x cycle, Python had urllib and urllib2 in the standard library and everyone said to ignore them and use Requests... which contains a urllib3, the maintainers of which refuse to ever add to the standard library.

Python had/has a bunch of "use Twisted instead" network protocol implementations. Python's standard library XML implementations carry a big warning to use the third-party `defusedxml` crate if you are processing untrusted data. etc. etc. etc.

I have next to no Java experience, but I vaguely remember it also having some similar cases of common wisdom being to ignore the standard library-provided solution.

That's fair.

I still think the "because it's not in the fast path" part of "Most software will not see a bottleneck because of bounds checking because it's not in the fast path" is a bit too much of a blanket statement and could detract from the admonition to benchmark very carefully before optimizing but, otherwise, I agree.

*nod* Give https://blog.readyset.io/bounds-checks/ a read.

They tried doing a comparison between ReadySet compiled normally and ReadySet with bounds checking removed so thoroughly that they needed to use a patched toolchain to achieve it and found the difference to be within the noise threshold.

Their conclusion was:

At the end of the day, it seems like at least for this kind of large-scale, complex application, the cost of pervasive runtime bounds checking is negligible. It’s tough to say precisely why this is, but my intuition is that CPU branch prediction is simply good enough in practice that the cost of the extra couple of instructions and a branch effectively ends up being zero - and compilers like LLVM are good enough at local optimizations to optimize most bounds checks away entirely. Not to mention, it’s likely that quite a few (if not the majority) of the bounds checks we removed are actually necessary, in that they’re validating some kind of user input or other edge conditions where we want to panic on an out of bounds access.

Before comparing strings or searching for a substring, normalize!

...and learn about the TR39 Skeleton Algorithm for Unicode Confusables. Far too few people writing spam-handling code know about that thing.

(Basically, it generates matching keys from arbitrary strings so that visually similar characters compare identical, so those Disqus/Facebook/etc. spam messages promoting things like BITCO1N pump-and-dumps or using esoteric Unicode characters to advertise work-from-home scams will be wasting their time trying to disguise their words.)

...and since it's based on a tabular plaintext definition file, you can write a simple parser and algorithm to work it in reverse and generate sample spam exploiting that approach if you want.

https://www.unicode.org/Public/security/latest/confusables.t...

and CD-ROM!

I think you mean Microsoft Windows's Joliet extensions to ISO9660 which, by the way, use UCS-2, not UTF-16. (Try generating an ISO on Linux (eg. using K3b) with the Joliet option enabled and watch as filenames with emoji outside the Basic Multilingual Plane cause the process to fail.)

The base ISO9660 filesystem uses bytewise-encoded filenames.

Technically, a superset would have to somehow Schrödinger's cat around \ in latin1 and ¥ in Shift-JIS being the same codepoint.

Unicode just took it upon themselves to reliably round-trip legacy text... thus the precomposed forms.

Most of the other complexity and technical debt is in the writing systems themselves.

Cookie consent is only necessary if you're sharing it with others (eg. ad networks, Google Analytics, etc.) or using it for "non-essential" functions (again, stuff like analytics). Sites just don't want the general public to realize that.

As for the mouse cursors, I don't think they qualify as personal information under the GDPR, but IANAL.

Yes. Give https://www.youtube.com/watch?v=_mZBa3sqTrI a watch... especially the "Oh my God! We've been hacked!" part at 36:20.

TL;DR: They had a transient glitch in their network switch and, because Windows uses UTF-16 when sending remote event logs over the wire, whenever it dropped a single byte, it had the effect of swapping the endianness of the messages, resulting in scary Chinese text in the logs.

You could get the same effect by naively applying byte-wise processing to UTF-16 or UTF-32, or having an off-by-one error.

UTF-8 is self-synchronizing so one-byte errors like that only lose you one character, rather than corrupting the entire stream going forward.

Give https://manishearth.github.io/blog/2017/01/15/breaking-our-l... a read and, ideally, the other things it mentions like https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/ and https://manishearth.github.io/blog/2017/01/14/stop-ascribing....

(Among other things, it points out that doing that to non-Latin text is liable to change pronunciations and meanings in other languages. For example, some languages use diacritics for voiced/unvoiced indication where your "normalization" could do things like "tick→dick" or "did→tit".)

(Did you ever notice that? B/P, D/T, V/F, G/K, J/CH, and Z/S form voiced/unvoiced pairs that could have been indicated with a single letter and a diacritic. Same mouth behaviour. It's just a question of whether you engage your vocal cords.)

I didn't know that unicode changes the definition of grapheme in backwards incompatible fashion annually, so software which works by grapheme count is probably inconsistent with other software using a different version of the standard anyway.

This is EXACTLY why Rust's standard library is blind to graphemes. Support for the case where your company requires a specially certified toolchain that lags five years behind Rust upstream is an explicit goal that they address by breaking the stuff that changes quickly out into minimal crates that can be audited, updated at a quicker pace without requiring toolchain updates, and which have the option to continue to support older compiler versions indefinitely.

*nod*

Rust was given as one of the examples and Rust's .len() behaviour is chosen based on three very reasonable concerns:

1. They want the String type to be available to embedded use-cases, where it's not reasonable to require the embedding of the quite large unicode tables needed to identify grapheme boundaries. (String is defined in the `alloc` module, which you can use in addition to `core` if your target has a heap allocator. It's just re-exported via `std`.)

2. They have a policy of not baking stuff that is defined by politics/fiat (eg. unicode codepoint assignments) into stuff that requires a compiler update to change. (Which is also why the standard library has no timezone handling.)

3. People need a convenient way to know how much memory/disk space to allocate to store a string verbatim. (Rust's `String` is just a newtype wrapper around `Vec<u8>` with restricted construction and added helper functions.)

That's why .len() counts bytes in Rust.

Just like with timezone definitions, Rust has a de facto standard place to find a grapheme-wise iterator... the unicode-segmentation crate.

I agree... you need discipline either way. It's just easier to wind up not realizing that you don't need to spend so much time on up-front optimization with Rust to produce a perfectly serviceable program. (Especially if you haven't internalized the whole "fearless refactoring" angle.)

1. Remember that Rust takes a Minimum Viable Product approach to 1.0 releases. There are experiments in adding stack traces in third-party error-handling crates that could eventually lead to additions to the standard library, but we've already gone through at least three iterations (error-chain, failure, anyhow, and possibly eyre) and already have two deprecated methods forever stuck on the standard library Error trait, so they're taking it slowly.

(Plus, true exception handling is messy for a language that cares so much about FFI. Look at the discussions surrounding how panic! unwinding should behave at FFI boundaries.)

2. I think completely automatic bubbling would be a misfeature, and the `?` operator is a good balance between concise ease-of-use and the Rust focus on "code is read far more often than it's written".

3. Again, the `?` operator. I think completely automatic unwrap would also be a massive footgun and comprehension hazard.

4. As mentioned in the other reply, try blocks are in development. (See my previous comment about Rust adopting a Minimum Viable Product approach to v1.0)

https://caniuse.rs/ is a great illustration of how far we've already come and how minimal Rust 1.0 was.

By building up design habits that lead to things passing the borrow checker on your first try.

In fact for me the only benefits of Go are single binary and startup time.

I hope you mean "compilation time" or "time to first prototype". My experience has been that, if a Rust binary doesn't start instantly, it's the fault of a specific library, not the language in general.

Also, they ARE working on single-binary deployment for Rust... they just have a much more "Take your time and do it right. Don't hack around ecosystem flaws" approach than Go.

If you're not cross-compiling/cross-deploying, Rust is already on par with non-cgo Go... you just need to use a musl-libc build target for Linux, specify RUSTFLAGS="-C target-feature=+crt-static" for Windows, etc. to get statically linked libc.

(The musl-libc targets are a simple `rustup target add` away if your only C dependency is the libc itself.)

I think they're all natural niches, because Rust's performance and ability to tightly control memory allocation are over-promoted and the strength of the type system as a means of ensuring correctness at compile time is underpromoted.

Especially web use is one of the biggest places you want to be able to rely on the compiler, aided by patterns like the newtype pattern and the typestate patterns, to catch as many potential DoS or security bugs as possible.

I use Rust as "Haskell, but without having to put up with writing Haskell". (Or, more commonly, I say that Rust is the sweet spot where you've taken pretty much everything functional programming has to offer before you start falling off a return-on-investment cliff.)

That's actually one of my reasons for writing in Rust whenever possible.

The Rust ecosystem has a broad collection of tooling for using Rust to extend other languages, so there's minimal chance something I wrote in Rust will have to be rewritten to use it in a project in another language.

See https://www.hobofan.com/rust-interop/ and https://areweextendingyet.github.io/

In Python's case, there's also maturin, which aims to make building and publishing Rust-based wheels as easy as Cargo makes doing so for Rust-based crates.

...plus, in the worst case, Rust CLI tools start very quickly, so forking a Rust process for poor man's FFI is more viable, and Serde plus a JSON Schema generator like schemars will streamline microservice-based options.

Yeah. The presence or absence of compiled extensions is a HUGE factor. Implementation design is also huge.

For example:

Geeqie is an image viewer/manager. It's a fork of the abandoned GQView and is apparently made in the image of Windows 9x-era ACDSee. It's written in C++ and uses GdkPixbuf.

My first prototype for re-creating Geeqie's collection view in PyQt so I could integrate it into a different kind of image management tool was twice as fast at loading thumbnails.

(Something on the order of 20s vs. 10s for loading somewhere around 7000 thumbnails on a 65W TDP dual-core Athlon from 2011.)

I tried quickly benchmarking GdkPixbuf, QImage, Pillow, and the Rust image crate against each other and found that, if anything, GdkPixbuf is a little faster than PyQt... but whatever Geeqie is doing cuts that performance in half.

(And that's with the Python version clamped to single-threaded image loading because I haven't yet written a smart I/O scheduler to allow parallelism on SSDs while avoiding thrashing rotating drives. Experiments with the QThreadPool-limiting line commented out show a speed-up from 10s to roughly 7s, consistent with how much of the overall system CPU capacity was taken up with other things like Firefox at the time.)

Odd. I wish Rust had Django's ecosystem so I could do more Rust web stuff, and I do use Rust for "shell scripts" and basic utility tasks, not for the performance, but for the compile-time guarantees.

I'd much rather pay a little extra up-front in resolving compiler errors, so I can know I'm not going to wake up to something like a six-hour batch job that failed 5% through or slam face-first into needing to debug an exception when I'm already in a rush and stressed over it.

Likewise, I find Rust's syntax much more pleasant than Go's... probably because I can see that Rust's syntactic complexity means things that have value to me, while Go's is just repetitive boilerplate to make my time a sacrifice on the altar of dogmatic language simplicity.

*nod* Aside from the obvious (compile-time guarantees beyond MyPy's ability to offer), the lack of pattern matching and an accompanying ecosystem that evolved in its presence is my number one discomfort when writing Python for things like my QWidget GUI frontends for Rust backends.

Java's ecosystem is moving away from checked exceptions because their "bolted onto the return type as a sidecar" design doesn't compose well with functional-style APIs.

Monadic error handling (Rust's Option<T>/Result<T, E>) avoids that issue by putting the error inside the return type as a normal value.

(Which is why, on multiple occasions, I've seen people call Result<T, E> "checked exceptions done right/properly".)