we have pid_t and gid_t and uid_t but no fd_t

they're all ints why did fds get left out of the typedef club qwq
@navi we have errno_t as well and nobody uses it ever
@humm it's part of annex K iirc and things just don't implement annex K at all

but yeah returning errno_t instead of 'int' would be a lot better

@navi @humm Why do yall think that the multiplication of integer types is a good thing?

It's boilerplate, it makes the code less clear, it bloats header files, and if I need to serialize it I need to add a sysdep test to know what size it is. And the benefits of having specific integer types are dubious at best, I've never seen a strong argument for them.

What makes you want fd_t and errno_t over int or, if anything, int32_t?

@ska @navi @humm honestly i'd be for it if itd disallow arithmetic operations (when's the last time it made sense to multiply a file descriptor ?), but it doesn't

on the otherhand, it just seems silly to have some have a typedef and some not

imo there shouldn't be any of those, but, hey

@SRAZKVT @ska @humm

i'd like function signatures to be more self-descriptive, not less
@navi @humm @ska sure, me too, but i'd also rather have a type that disallows useless operations for those, or for everything to be consistent and at least all be typedef'd
@SRAZKVT @humm @ska

oh, yes, having it be properly a new type instead of an alias would be great -- and i think celeste even proposed that to wg14, a "stronger" kind of typedef

dunno how that went tho

@SRAZKVT
>when's the last time it made sense to multiply a file descriptor ?

you get a file descriptor as a decimal number string, you transform it to int by repeatedly multiplying it by 10 and adding digits

@ska @navi

@humm @ska @navi yes for special cases like this you should be able to extract and replace the actual int, but it should be explicit

my point is that code like open(...) * 3 is semantic nonsense and shouldn't be allowed

@ska @navi @humm

Allow expressing semantics and intentions more clearly. But I don't think it really helps much without having a more complex type system than C has. Eg. casting from fd_t to uid_t or adding 1 to a fd_t value is semantically nonsense and would ideally cause a warning.

@sertonix @ska @navi @humm well technically you could do that if they're defined as a struct with just an int inside, but that's a hack and not in the c standard

@SRAZKVT @ska @navi @humm

I know it and I hate it :)

@ska @humm

because it isn't any random int, it's a file descriptor, or an error, it has semantic meaning beyond the fact that it's represented by a number

you wouldn't do arithmetic with any of them, you wouldn't type a random literal to a function that takes a fd -- the fd represents and object, not a number by itself
@ska @humm


> It's boilerplate,
how? it's not like you're typing more, just, a different identifier

> it makes the code less clear
disagree, `copy_range(fd_t in, fd_t out, size_t size);` is a lot clearer to me than `copy_range(int fd_in, int fd_out, size_t size);`

> and if I need to serialize it I need to add a sysdep test to know what size it is
nothing stops it from being specified as an `int` under the hood, and otherwise, `sizeof()`?

@navi @humm I agree with almost everything here but if it's specified as an int under the hood, there's no reason for it not to be officially an int.

Boilerplate wasn't the right word; I rather mean burden of knowledge. Mental boilerplate.

Where I disagree is on our perception of clarity. copy_range(int fd_in, int fd_out, size_t size is clear enough to me, and doesn't burden the language with typedefs in order to say the same thing; documentation is obtained by clever use of (completely optional in a prototype) variable names.

Again, I think at this point it's just a question of taste.

@ska @humm

> Again, I think at this point it's just a question of taste.

i agree yeah
@navi @humm @ska Its nice if tab-completing the wrong variable result in "Error: xxx expected fd_t got errno_t"
@navi @humm @ska well the last one you do when you need specifically stdin or stdout or stderr. but yea
@SRAZKVT @humm @ska

i use STDOUT_FILENO / STDIN_FILENO, actually, it's harder to mess up

@navi @humm @SRAZKVT That's exactly what I call boilerplate 😅 I don't see much value in this kind of macro that will never change. To me 0 in a fd context is just as readable as STDIN_FILENO while being shorter, so I prefer it - and if the fd context isn't clear, then the function needs to make it clearer. But I'm willing to accept it's a pure question of taste.

(Edit: typo)

@navi @humm Okay that makes sense, but it's in the nature of C to use int as a generic handle, and because it's an int doesn't mean you're going to do arithmetic with it. The integers I do arithmetic with are almost exclusively uint32_t, because 1. I want to know what their range is, and 2. in system programming, I never need negative numbers.

int is terrible for arithmetic for these reasons, and on the other hand it's a pretty good type for a handle: wide enough for all your needs and you don't really care if it's 32 or 64 bits, can use negative numbers for e.g. error values, has an ordering so you can use it as a key for sorting and logarithmic searches, etc.

This is a volcanic take, but I'd be okay with removing arithmetic operations from int (as long as you keep comparisons) and making it a handle-only type 😝

@ska @navi @humm "All the world's a VAX.". Don't fall into that trap. It's OK for code specific to one architecture, but for anything that has to work on multiple architectures you need to deal with possible weirdness. Big-endian is the least of it.
@tknarr @navi @humm Right, saying that types that are explicitly specified as integer types and typedef'd as int could be int instead is making assumptions that will break on some architecture, I'm sure 🤪
@ska @tknarr @navi @humm
There are some deeply bizarre architectures out there. It wouldn't surprise me at all.
@kirtai @ska @navi @humm I remember a while back looking up all the architectures Unix had been implemented on up through the late 90s or so. I though stuff like 36-bit int and middle-endian was the worst, but I was sadly mistaken.
@kirtai @tknarr @navi @humm I honestly don't think you can find me an architecture where an int is not an int, but don't let that discourage you from looking

@navi @humm @ska For functions returning an fd or -1 on error, I see code check fd >= 0. Or code which checks fd <= 2 to see if it's one of the standard streams. close_range (which I think is a misfeature) works on fds >= first and <= last. poll allows you to easily disable polling one of the fds in the array by setting it to ~fd.

All of these require fds to be a numeric type if not an integer type.

@koorogi @navi @humm I see these more as shortcuts than "official" ways to do things. For errors, there could be an FD_ERROR special value. For standard streams, there could me a macro checking them. For close_range, well, we agree it should not exist. For select (not poll, which takes a list of struct pollfd!) there could be macros to enable or disable an individual fd.

I mean, I agree, fd being a numerical type is practical and I like the fact that it's an int, but I don't think your arguments are the deciding factor.

@ska @koorogi @humm

i don't even mind it being numerical, uid_t and pid_t and such return -1 on failure just fine as well

@navi @humm @ska I was trying to answer the question of why you would do arithmetic on an fd, but I realize what I was trying to get at got lost.

The point was supposed to be that < and > are arithmetic operations -- they're equivalent to performing subtraction and checking the sign of the result.

@koorogi @navi @humm No, they're comparison operators, which have a much lighter requirement than arithmetic operations: they only need an ordered set, whereas arithmetic needs a mathematical group/field/ring.

Substraction + sign check requires a stricter hypothesis than direct comparison between two elements does.

Of course, on a computer, this mathematical difference doesn't really matter, because it's all integers inside anyway, but if we're writing a spec, it's worth knowing how strong you want the guarantees to be.

@ska @navi @humm in math, yes, but the assumed context here was C.
@ska @navi @humm Semantics. A file descriptor isn't an int, even if it's represented as one. And remember that whole mess when people's assumption that time_t was an int, specifically a 32-bit signed int, broke when we started using 64-bit systems? Using specific types helps emphasize that you can't make assumptions like that.

@ska
>I need to add a sysdep test to know what size it is

Now this is something that annoys be about all the typedefs for which it isn’t specified what type exactly they are: They don’t get corresponding macros in <limits.h> and <inttypes.h>. For errno_t and a hypothetical fd_t it’s “fine” (if mentally annoying) because you know they’re int, but how do you print a uid_t? In C89, converting to unsigned long and using %lu is safe. From C99 to C17, converting to uintmax_t and using %ju is safe. I’m guessing that the POSIX edition that rebases on C23 will keep uintmax_t safe by prohibiting uid_t from being an extended integer type, but maybe not.

@navi

@humm @navi That is honestly on the insanity of wanting to stick to the printf format despite having so many types. But yes, there is a real problem underneath, which is you don't know if all these types have the same size.

It would be funny if needing to convert to uintmax_t and using %ju was finally the loss of convenience that would make some people realize that oh, the printf format sucks, actually.

@navi pid_t and gid_t and uid_t are the consequence of historical disagreements about the underlying type and abstracting that away so systems with non matching definitions could both conform.

There has never been a disagreement on the type for fds.

@dalias @navi

Yeah, they're long long. /s