Then why do those "asserts" have return error codes associated with them? A failing assert shouldn't use normal error reporting mechanisms, and it most certainly shouldn't return a specific error code implying that assertion failures can (and should) be handled.
For the same reason read(3) returns EBADF if you pass it a bad file descriptor. Why doesn't the libc wrapper around the syscall just abort() if I give it a bad file descriptor?
In systemd-networkd, the "asserts" assert the pre-conditions of a successful call to that function (in dhcp6-option.c). It's up to the caller (in sd-dhcp6-lease.c) to validate the users input, and ensure that it's passing valid arguments to the functions it's calling.
Building software in layers, and making use of "internal" libraries is a valid software engineering strategy for managing complexity.
> For the same reason read(3) returns EBADF if you pass it a bad file descriptor. Why doesn't the libc wrapper around the syscall just abort() if I give it a bad file descriptor?
Because, unfortunately, POSIX requires it to do that. But that's a bad decision on the part of POSIX, and we shouldn't perpetuate that when we have the choice. Assertions that can be "handled" are not assertions - they're errors. By returning an error code, you're saying "it's okay to pass bad things here without checking, and check the error code afterwards". If it's not actually okay, because validation is not reliable, then you're just enabling writing bad, unreliable code that shouldn't even be possible to write.
The "bad descriptor" check in read() is a good example. You can never rely on EBADF unless you fully control every line of code that ever runs in your process, because a descriptor might be accidentally valid - it might be a descriptor that was closed, but then got reused for something else (and POSIX explicitly requires reuse of file descriptors). So checking for EBADF to e.g. detect closed descriptors and do something about that will mostly work, but it will sporadically break by doing something on the wrong descriptor. Therefore, it would be strictly better if read() just called abort(), and thereby forced the caller to use some other mechanism to keep track of what's valid and what isn't - the one that the caller can implement reliably, if it can.