Hacker Newsnew | past | comments | ask | show | jobs | submit | creatonez's commentslogin

Java does fine on memory safety, but does not do great on null safety (and overall invariant protection / "make invalid states unrepresentative" ethos), has difficult to harden concurrency primitives, and won't be adopted in many scenarios due to runtime cost and performance pitfalls. Future Valhalla work fixes some of these issues, but leaves many things spiky.

I dislike Java's abstraction-through-indirection approach, which is related to the non-representable invalid states you mention. But I think it's more of a matter of taste.

Somewhat controversially, I think Java is actually doing fine on null safety: it uses the same approach for it as it does for array index safety. The latter is a problem for any language with arrays without dependent types: out-of-bounds accesses (if detectable at all) result in exceptions (often named differently because exceptions are controversial).

Java's advantage here is that it doesn't pretend that it doesn't have exceptions. I think it's quite rare to take down a service because handling a specific request resulted in an exception. Catching exceptions, logging them, and continuing seems to be rather common. It's not like Rust and Go, where unexpected panics in libraries are often treated as security vulnerabilities because panics are expected to take down entire services, instead of just stopping processing of the current request.


I'm not talking about null safety in the sense of null pointers. Null pointers and out of bound pointers are still in the realm of memory safety, which of course Java has solved for the most part.

Proper null safety (sometimes called void safety) is to actually systematically eliminate null values, to force in the type system a path of either handling or explicitly crashing. This is what many newer expressive multi-paradigm languages have been able to achieve (and something functional programming languages have been doing for ages), but remains out of reach for Java. Java does throw an exception on errant null value access, but allows the programmer to forget to handle it by making it a `RuntimeException`, and by the time you might try to handle it, you've lost all of the semantics of what went wrong - what value was actually missing and what a missing value truly means in the domain.

> Catching exceptions, logging them, and continuing seems to be rather common. It's not like Rust and Go, where unexpected panics in libraries are often treated as security vulnerabilities because panics are expected to take down entire services, instead of just stopping processing of the current request.

Comparing exceptions to panics is a category error. Rust for example has great facilities for bubbling up errors as values. Part of why you want to avoid panicking so much is that you don't need to do it, because it is just as easy to create structured errors that can be ignored by the consumer if needed. Java exceptions should be compared to how errors are actually handled in Rust code, it turns out they end up being fairly similar in what you get out of it.


Java introduced Optional to remove nulls. It also introduced a bunch of things to make it behave like functional languages. You can use records for immutable data, sealed interfaces for domain states, you can switch on the sealed interface for pattern matching, use the sealed interfaces + consumers or a command pattern to remove exception handling and have errors as values.

using an instance of a sealed class in a switch expression also has the nice property that the compiler will produce an error if the cases are incomplete (and as such there's also no need for a default case). So a good case for the "make invalid states unrepresentable" argument.

I understood what you meant. I just disagree about priorities. Conceptually, every array access (absent dependent types) can produce a null value because the index might be out of bounds. Languages that eliminate null values in other areas typically fail to deal with the array indexing issue at the type level, which seems at least as prevalent in real-world code as null pointer deferences, if not more so.

Regarding the category error, on many platforms, Rust panics use the same underlying implementation mechanism as C++ exceptions. In general, Rust library code is expected to be panic-safe. Some well-known Rust tools use panics for control flow (in the same way one would abuse exceptions). The standard test framework depends on recoverable panics, if I recall correctly. The Rust language gives exceptions a different name and does not provide convenient syntax for handling them, but it still has to deal with the baggage associated with them, and so do Rust library authors who do not want to place restrictions on how their code is reused. It's not necessarily a bad approach, to be clear: avoiding out-of-bounds indexing errors completely is hard.


unwrap

There's nothing stopping you from writing code that is completely functional and devoid of nulls these days. It's just that java obviously still allows nulls if someone needs to use them (partly for interoperability with legacy code)

But if you're going to argue about the mere presence of null being problematic, you might as well complain about the ability to use "unsafe" code in Rust too.


That's not a reason for this page to ignore the Java ecosystem, which extremely fits with Prossimo's mission.

It's at the kernel level. Each process has its own current working directory. On Linux, these CWD values are exposed at `/proc/[...]/cwd`. This value affects the resolution of relative paths in filesystem operations at a syscall level.

It’s also generally a shell builtin. Though you do find an executable called cd too for compatibility reasons.

Interesting. I've been using Unix systems for 30 years and never noticed this.

On my Fedora system, /usr/bin/cd is just a shell script that invokes the shell builtin:

  #!/usr/bin/sh
  builtin cd "$@"
I suppose it could be useful for testing whether a directory exists with search permissions for the current user safely in a multithreaded program that relies on the current directory remaining constant.

Yeah, it's typically a shell built-in since you'd want cd to change the cwd for the shell process itself. Child processes (like commands being executed in the shell) can inherit the parent shell's cwd but AFAIK the opposite isn't true.

Wait, how did the `cd` executable used to work in old Unix? Did it instruct the kernel to reassign the CWD of the parent process?

The original UNIX shell (Thompson Shell) had chdir as a builtin, so I’d wager it’s always been a builtin.

https://en.wikipedia.org/wiki/Thompson_shell


...Eight seconds for a hundred matches? What does your code look like?

My bad, I should not read AI generated code while drunk at a xmas party. That's the total run time for 10000 iterations.

Average time for 100 tests is hence 0.8 ms. Completely normal, and absolutely acceptable, especially for an operation as rare as routing.

Letting my previous comment as-is for historical purposes. And to remind myself I'm a dumbass.


In the near future I fear there may be laws about “LLMing while drunk” after enough rogue LLM agents vibe coded while drunk cause widespread havoc. You know folks harassing exs or trying to hack military depos to get a tank.

Actually that’d be a fun sci-fi book.


The Linux kernel was not born obsolete on arrival like the X Window System was. At least, not fully obsolete.

It will be obsolete someday, anyways.


“Obsolete” means that a replacement exists which is as good as, or better than, the thing it aims to replace, in every significant way.

X11 is far from obsolete.


In what way was X11 obsolete in 1987?

Google doesn't use chrony specifically, just an algorithm that is somewhat chrony-like (but very different in other ways). It's called Google TrueTime.

Oh right. Their cloud-init script uninstalls NTP and installs chrony each time our VMs boot

Ah yeah. For VPS tenants it makes sense they would default to Chrony. They have a public facing pool of NTP servers at `time.google.com`, and for tenant use they provide `metadata.google.internal`, which is probably where the Chrony config file points. IIRC TrueTime is not actually open source and is only used internally on their infrastructure.

This might be referring to k-anonymity where you truncate the hash so that it matches about 1000 hashes, then the client matches against that list. Which makes it so the operator can't really narrow down what exact license plates correspond to which searches.

Where are you seeing the other posts?

My understanding is that people who connect specifically to the NIST ensemble in Boulder (often via a direct fiber hookup rather than using the internet) are doing so because they are running a scientific experiment that relies on that specific clock. When your use case is sensitive enough, it's not directly interchangable with other clocks.

Everyone else is already connecting to load balanced services that rotate through many servers, or have set up their own load balancing / fallbacks. The mistakenly hardcoded configurations should probably be shaken loose anyways.


Wait, does that mean Manifest v3 is so neutered that it can't load a `<script>` tag into the page if an extension needed to?

If so, I feel like something that limited is hardly even a browser extension interface in the traditional sense.


That is correct. You can not inject external scripts. You can fetch from a remote and inject through the content script though, but the content and service worker code is known at review time.

So you can still do everything you could before, but it’s not as hidden anymore


Most browser extensions don’t need to insert script tags that point to arbitrary URLs on the internet. You can inject scripts that are bundled with the extension (you don’t even need to use an actual script tag). This is one part of manifest v3 that I think was actually a good change - ad blockers don’t do this so I don’t think Google had an ulterior motive for this particular limitation.

> there are plenty things you can't express in jsdoc but can in typescript

This isn't really true anymore, they have systematically added pretty much every type system feature to the JSDoc-like syntax.


Having JSDoc-like syntax isn't the same as it being fully supported. If you have a large enough codebase, you'll likely find a few cases where things work in TypeScript but its equivalent somehow fails type check in JSDoc.

> If you have a large enough codebase, you'll likely find a few cases where things work in TypeScript but its equivalent somehow fails type check in JSDoc.

You keep repeating this throughout the thread. Can you give an example?


Not that person, but is there an easy way to write something like a basic semver?

export type SemVer = `${number}.${number}.${number}`;

Could you extend it to work with regex groups like:

export const SemVerRegex = /^(?<major>0|[1-9]\d)\.(?<minor>0|[1-9]\d)\.(?<patch>0|[1-9]\d)(?:-((?:0|[1-9]\d|\d[a-zA-Z-][0-9a-zA-Z-])(?:\.(?:0|[1-9]\d|\d[a-zA-Z-][0-9a-zA-Z-]))))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;

Could the groups be extracted so you the type back if you ran the regex on a string like: "1.2.3", or "1.2.3-prerelease"?


I don't understand the second part of your comment (that's a value, not a type), but the first part looks like this:

    /** @typedef {`${number}.${number}.${number}`} SemVer */
Here's a playground: https://www.typescriptlang.org/play/?filetype=js#code/PQKhAI...

Thanks for the example, I had a good play around with it.

The second part of my comment is a value yes, but it's implicitly typed by typescript automatically. I was asking about how to use that type (and it's internals) in jsdoc.


> it's implicitly typed by typescript automatically

What do you mean by this? Can you share an example?

Regular expressions do not act as type guards, and there's no way to express a type that allows one regular expression but rejects another (they're all just `RegExp`): https://www.typescriptlang.org/play/?target=5#code/MYewdgzgL...


You wouldn't use `test()` like in your example.

`test()` only returns a boolean, you want to look at `exec()` which returns the result of the regular expression (typed as: `RegExpExecArray | null` which you can narrow down to `RegExpExecArray` by checking if the result is null or not).

RegExpExecArray gives you a structure that looks different between the jsdoc version and the typescript version.

The typescript version has `.groups` inside RegExpExecArray.

You can use that as is, or you can add some extra type utilities to extract the named groups from inside the regex. (If you look inside typescript's issues on github you'll find a whole bunch of them that people have wanted typescript to include by default).

There's a few regex PRs to add extraction and syntax checking to typescript by default, but they're delayed until after the compiler switch from ts to go. (but there's porting to go in the PRs anyway).


Thanks for the reply.

> RegExpExecArray gives you a structure that looks different between the jsdoc version and the typescript version.

> The typescript version has `.groups` inside RegExpExecArray.

The JSDoc version has `.groups` too, and it appears to be typed identically to the TypeScript version given similar configuration.

Here's the TypeScript version: https://www.typescriptlang.org/play/?target=5&strictNullChec...

And here's the JSDoc version: https://www.typescriptlang.org/play/?target=5&strictNullChec...

Could you show me what you mean in the playground? You can change it to JSDoc mode by selecting "JavaScript" in the "Lang" dropdown in the "TS Config" tab.


Your examples has groups only typed as { [key: string]: string }.

It isn't narrowed down to the actual named capture groups inside the regex.

For the whole semver example, we'd want something typed as: { major: string, minor: string, patch: string }.

(sidenote for this semver example and not regex in general: they will be strings because a major of "01" and a patch of "3-prerelease" are both valid. You can parse them into ints later depending on what you're actually doing with the semver)

---

For an example of a type narrowed regex in the ts playground, I'll take the lazy route and point you to an example someone else has made:

https://www.typescriptlang.org/play/?ssl=1&ssc=39&pln=1&pc=3...

You can use a utility library (e.g. ts-regexp, arkregex, etc...), you can use one of the utilities that are shared around the typescript spaces online for the one part you need, or you can make your own utility type.

---

For my use-case, I just want to know that I can change either my code or regex and I'll be alerted before runtime or buildtime if I've forgotten to account for something.

To carry on with the semver example, if a fourth piece of data was added to the incoming string (e.g. product name, build timestamp, build variant, etc...) and I forgot to update both the code and the regex together and instead only updated one, I'd want an alert in my IDE to tell me that I forgot to update the other one.


The ts-regexp package `infer`s out of template literal types to do its magic. That's possible from JSDoc too: https://www.typescriptlang.org/play/?filetype=js#code/PQKhCg...

The behavior of ts-regexp is not especially related to the `RegExpExecArray` type. That package uses unsafe assertions to implement its API.

The only thing that requires TypeScript syntax in the playground you linked is the non-null assertions. Here's that same code in JSDoc mode with the non-null assertions removed (`strictNullChecks` is disabled instead): https://www.typescriptlang.org/play/?filetype=js#code/JYWwDg...

---

> For my use-case, I just want to know that I can change either my code or regex and I'll be alerted before runtime or buildtime if I've forgotten to account for something.

If you can show a concrete example what you want using TypeScript syntax then I'm pretty sure I could port it to JSDoc syntax. (To be clear I'd personally much rather write the TypeScript syntax as I find JSDoc comments annoying & verbose, but they're plenty powerful, being nearly isomorphic to TypeScript annotations.)


it is possible to do many of these with @typedef, but it gets difficult with JSDoc very quickly. In TypeScript you can easily create multi-line type aliases. Not quite so in JSDoc.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: