The C’s prefix syntax has been used for so long that .* looks weird, but a postfix operator is the right place for a dereference. It chains nicely, and if you squint, it’s consistent with field access syntax that is also a form of dereference.
I was actually coming here to say that I really appreciate the well-written, intro-level post. I’ve been programming for almost 15 years professionally but I’ve spent all that time in high-level languages and am still really “getting” pointers. Programming is such an enormous field, there’s always something to learn. 🙂
Funny, I quite like it. If I ever create a language of my own I’d probably use that.
Maybe it is just from being exposed to Go, with it’s “ptr.(type)” and “ptr.(int)” forms for type switch and assertion which make it seem a bit more comfortable.
Adding to snej’s comment, in Pascal you write p^ to dereference a pointer and r.f to select a field from a record. If you have a pointer to a record, you can write pr^.f. It’s simple, orthogonal and logical.
The C language made the ergonomic mistake of using a prefix operator to dereference a pointer (*p). To get a field from a struct pointer, this means you write (*rp).f. That sucks so badly that C has a short-form for this: you can write rp->f instead. It’s an over-complicated bad design.
Zig collapses the C operators . and -> into a single . operator, and replaces C’s one-character prefix pointer-deref operator * with a two-character postfix operator .*. This is a bit more ergonomic than C, but it’s still a bad design, when compared to Pascal.
Not to defend prefix dereference, but it’s not like you can’t fix C’s mistakes. Rust supports rp.f just fine by auto-dereferencing until it finds the right type and doesn’t need -> or an explicit deref for trivial accesses
The order of operations issue still exists, but to a much lesser extent.
Huh, I wonder if that Pascal design is why the Haskell lens packages uses ^. as an alias for view. As in, record ^. foo . bar . baz = view (foo . bar . baz) record accesses a value nested through several layers of record.
BCPL has two forms of the dereference operator, prefix unary and infix binary. The latter does double duty for structure access and array indexing.
C BCPL
*ptr !ptr
ptr->field ptr!field
ptr[n] ptr!n
In pre-K&R C before the type safety of structures was improved, C’s ptr->field was a lot more like BCPL’s ptr!field, but with a nicer way to declare the field offsets and types. But like BCPL, the left hand operand could be anything. For example, in the 6th edition kernel there are expressions (after macro expansion) like 0177560->integ for accessing hardware registers.
The lv / rv thing was from Strachey’s idea of lvalues and rvalues. The CPL project seems to have struggled a lot with references, list processing, and record types. The whole effort fell apart before they worked out a solution.
Dunno how much users in the USA kept up with revisions to BCPL after Martin Richards returned from Cambridge Massachusetts to Cambridge England.
The C’s prefix syntax has been used for so long that
.*looks weird, but a postfix operator is the right place for a dereference. It chains nicely, and if you squint, it’s consistent with field access syntax that is also a form of dereference.The “.” is weird, though — it makes it look like a struct field. Pascal’s postfix “^” operator is clearer.
(An article explaining pointer dereferencing seems maybe a bit basic for lobste.rs…?)
Think about it as just “field access, but with a wildcard”
Instead of
.fieldyou do.*which matches all fields, and suddenly it feels more natural than another “special syntax”According to this mental model,
.?should return arbitrary one field :DIt should return all fields whose names are one character :)
I was actually coming here to say that I really appreciate the well-written, intro-level post. I’ve been programming for almost 15 years professionally but I’ve spent all that time in high-level languages and am still really “getting” pointers. Programming is such an enormous field, there’s always something to learn. 🙂
Do you feel the same way about calling methods with
.?No, a method is an element of the pointed-to struct, just like a field access. It’s the use of “.” without any field that seems weird to me.
Funny, I quite like it. If I ever create a language of my own I’d probably use that.
Maybe it is just from being exposed to Go, with it’s “ptr.(type)” and “ptr.(int)” forms for type switch and assertion which make it seem a bit more comfortable.
Adding to snej’s comment, in Pascal you write
p^to dereference a pointer andr.fto select a field from a record. If you have a pointer to a record, you can writepr^.f. It’s simple, orthogonal and logical.The C language made the ergonomic mistake of using a prefix operator to dereference a pointer (
*p). To get a field from a struct pointer, this means you write(*rp).f. That sucks so badly that C has a short-form for this: you can writerp->finstead. It’s an over-complicated bad design.Zig collapses the C operators
.and->into a single.operator, and replaces C’s one-character prefix pointer-deref operator*with a two-character postfix operator.*. This is a bit more ergonomic than C, but it’s still a bad design, when compared to Pascal.Not to defend prefix dereference, but it’s not like you can’t fix C’s mistakes. Rust supports
rp.fjust fine by auto-dereferencing until it finds the right type and doesn’t need->or an explicit deref for trivial accessesThe order of operations issue still exists, but to a much lesser extent.
Huh, I wonder if that Pascal design is why the Haskell lens packages uses
^.as an alias forview. As in,record ^. foo . bar . baz=view (foo . bar . baz) recordaccesses a value nested through several layers of record.BCPL has two forms of the dereference operator, prefix unary and infix binary. The latter does double duty for structure access and array indexing.
In pre-K&R C before the type safety of structures was improved, C’s
ptr->fieldwas a lot more like BCPL’sptr!field, but with a nicer way to declare the field offsets and types. But like BCPL, the left hand operand could be anything. For example, in the 6th edition kernel there are expressions (after macro expansion) like 0177560->integ for accessing hardware registers.Late 1960s versions of BCPL had the rather awkward
lvandrvoperators for taking addresses and indirection; they became @ and ! in the early 1970s. https://www.softwarepreservation.org/projects/BCPL/The lv / rv thing was from Strachey’s idea of lvalues and rvalues. The CPL project seems to have struggled a lot with references, list processing, and record types. The whole effort fell apart before they worked out a solution.
Dunno how much users in the USA kept up with revisions to BCPL after Martin Richards returned from Cambridge Massachusetts to Cambridge England.
I like how Nim uses
p[]for dereference. It’s like indexing an array, but without an index.I’m a big fan of the .* syntax. It instantly clicked for me and now I want it in every other language with pointers.