News for hackers. Stuff that matters.
Re:Cdecl: C Gibberish ↔ English by masklinn on Saturday April 23, 2022 @10:18AM (#31134474)
> The biggest problem with C's pointer syntax is that it's prefix.

The biggest problem with C's pointer syntax is that it's prefix to the value.

    int p*
is just as horrible as

    int *p
because in both cases, it obscures that the type of p is "pointer to integer".

C's array and function syntax have exactly the same issue, the entire thing is back-asswards.

Let me demonstrate by converting your examples to a more regular (and logical) language[0]:

    let i: i32 // integer
    let i: Box<i32> // pointer to integer
    let i: fn() -> i32 // function returning an integer
    let i: Box<fn() -> i32> // pointer to a function returning an integer
    let i: fn() -> Box<i32> // function which returns a pointer to an 
    let a: [i32;10] // array of 10 integers
    let a: [Box<i32>;10] // array of ten pointers
    let a: Box<[i32;10]> // pointer to an array
    let a: [fn() -> Box<i32>;10] // array of ten functions that return a pointer
Even if you want to shorthand pointers (because you have only one and want it to be built-in), it remains significantly more readable than both C actual and your version.

[0] which doesn't mean perfect e.g. one could argue against the special syntax for arrays

Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @10:51AM (#31134733)
You changed your post, so this is a reply to the changes.

Boxes are containers, not pointers or references. Let's look at all of those those using the star syntax and operator inside of an unsafe block? You'll end up with stuff like:

    fn main() {
        let mut a = [0i32; 10];
        let p: *mut [i32] = &mut a;

        // Rust did NOT solve this problem
        let x: i32 = unsafe { (*p)[1] };

        print!("{x}\n");
    }
Note the annoying parentheses because they used prefix syntax for pointers, again.
Re:Cdecl: C Gibberish ↔ English by masklinn on Saturday April 23, 2022 @11:33AM (#31135125)
> Boxes are containers, not pointers or references.

There is no actual difference between the two. Pointers and references could have been `Pointer<T>` and `Ref<T>` with the exact same ABI. They're not for UX reasons, one of similarity with C, and one of syntactic overhead.

> // Rust did NOT solve this problem

> let x: i32 = unsafe { (*p)[1] };

That is a debate of precedence, which is a very different concern than what I was replying about, and what TFA is about: the absurd mess that are C type declarations.

Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @12:17PM (#31135604)
> There is no actual difference between the two.

Rust has raw pointers, references, slices, and arrays, each with special syntax in type/variable declarations. You're conflating those with Boxes, and it isn't adding clarity to the discussion. It's not like C++ removed this problem now that people use `std::unique_ptr<T>`.

Rust's raw pointer syntax is exactly as bad as C's, because it's the same syntax. Yes, it could use some other syntax like `Ptr<T>` for declaration, but it doesn't. And that still says nothing about what operator syntax to use when dererencing the pointer.

Perhaps it could be:

    let p: Ptr<T> = whatever;
    let x = p.deref(); // suffix syntax!
> That is a debate of precedence, which is a very different concern

It's not a different concern, and it's a problem no matter what the precedence. If prefix pointer syntax binds tightly, you'll need parens in one case. If it binds loosely, you'll need parens in another case:

    *(binds_tightly.foo[10]);  // subscript the array first because star binds tightly
    (*binds_loosely).bar[10];  // dereference the pointer first because star binds loosely
However, if pointer syntax is suffix (like arrays, function calls, and field accesses), you simply don't need parens in either case. The operations are just in order.

> (the absurd mess that are C type declarations)

Hating on C is trendy, that's fine. But a lot of people are confused about what the problem really is. It could be fixed with one change, but since people can't see it clearly, they change everything. And in cases like Go or Rust, they change almost everything and still don't solve the problem.

A language could achieve C's goal of "declaration follows usage" if pointers had suffix syntax. The prefix pointer syntax is where crazy "winding rules" and "Gibberish to English" translators come in.

Re:Cdecl: C Gibberish ↔ English by xigoi on Sunday April 24, 2022 @07:53AM (#31143187)
“declaration follows usage” is not a worthy goal. It's unreadable and doesn't allow for creating new parametrized types.
Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @10:33AM (#31134609)
It's clear you prefer the final type as suffix. That just means you need another syntax or keyword (such as "let") to introduce a variable declaration.

However function calls, array subscripts, and field accesses are the same "kind" of operation as dereferencing a pointer. Put the type last if it makes you happy, but you'll have "horrible" syntax if you don't treat pointers the same as the others:

    something.bar[10]()  // access the field, subscript the array, call the function
    something.foo@       // access the field, dereference the pointer
Re:Cdecl: C Gibberish ↔ English by mananaysiempre on Saturday April 23, 2022 @11:49AM (#31135302)
> It's clear you prefer the final type as suffix. That just means you need another syntax or keyword (such as "let") to introduce a variable declaration.

Not really? Assuming type and variable names are lexically distinguishable via the lexer hack or, say, capitalization. Here’s an LR(1) grammar (in Grammophone[1] syntax) with postfix types and dereferencing:

  stmt -> init ; | expr ; .
  
  init -> decl | decl = expr .
  decl -> dtor type .
  dtor -> ambi | dfun .
  dfun -> ambi ( decl ) | ambi ( darg ) | dfun ^ | dfun ( ) .
  darg -> decl , | darg decl , .
  
  expr -> summ | summ = expr .
  summ -> prod | summ + prod .
  prod -> post | prod * post .
  post -> ambi | efun .
  efun -> ambi ( expr ) | ambi ( earg ) | efun ^ | efun ( ) .
  earg -> expr , | earg expr , .

  ambi -> name | ambi ^ | ambi ( ) .

  # Stub
  type -> int .
  name -> x .
It lacks the comma operator or Go-style grouping of multiple variables before a single type, but adding either one is easy and both is possible, though in the latter case the “ambi” hack would need to be extended. I’m willing to believe you could even do this with LL(1) / recursive descent, but the grammar structure would probably end up rather horrible.

Whether this is a smart idea as far as ergonomics of syntax errors go, I don’t know.

[1] https://mdaines.github.io/grammophone/

Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @12:29PM (#31135720)
> Assuming type and variable names are lexically distinguishable via the lexer hack or, say, capitalization.

Hah, I appreciate your point and careful reply, but that's quite an assumption :-)

I think using capitalization does qualify as "another syntax", which I was cautious to qualify. And (as you know) the "lexer hack" means you need to declare your types before you use them, which is a bummer for mutually recursive types.

Anyways, I'll concede that you're right in that you can do it, but I don't think I was very wrong :-)

Re:Cdecl: C Gibberish ↔ English by mananaysiempre on Saturday April 23, 2022 @07:50PM (#31139872)
I can honestly say I wasn’t motivated by how wrong I felt you were, but by how I have been fiddling with declaration syntaxes like this over the last several months :)

Which actually led me to make an error here. The starting point was C syntax so I thought assuming a lexer hack was fair game, but the grammar above doesn’t actually need it! It doesn’t need one even if you add parameterized types with the same syntax as function calls. It only needs it if you want to write function calls as juxtaposition without parens ML style, use infix syntax for type application, or (and that is what I have been fiddling with) drop the semicolons.

(D'oh, I knew I was forgetting something! It was parenthesized expressions. And apparently I also screwed up optional comma termination. And curried calls.)

So, for example, you can add these productions to the grammar above and it still stays LR(1):

  # Remove lexer hack, add parameterized types
  type -> name | type ( ) | type ( type ) | type ( targ ) | type ( targ type ) .
  targ -> type , | targ type , .
  
  # Fix the stupid
  dfun -> ambi ( darg decl ) | dfun ( decl ) | dfun ( darg ) | dfun ( darg decl ) .
  efun -> ambi ( earg expr ) | efun ( expr ) | efun ( earg ) | efun ( earg expr ) | ( expr ) .
I’m not really a fan of this variant of “declaration mirrors use”, mind you. Type ascription usually requires some other syntax (both C and Go suffer from this); custom types that don’t have special punctuation are awkward and even tuples will be kind of forced. Typing the occasional colon does not seem like a bad tradeoff in comparison. I’d love to see these problems solved, though.
Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @01:32PM (#31136311)
By the way, I'm kind of surprised you didn't use 4-letter names for your stub section at the bottom. How can you suffer the inconsistency in such an otherwise consistent grammar? :-)
Re:Cdecl: C Gibberish ↔ English by mananaysiempre on Saturday April 23, 2022 @03:31PM (#31137626)
I console myself with the idea that they are terminals, and I can see if I forgot to define a nonterminal by checking that no examples contain four-letter words. Choosing a name for the sum nonterminal was much more painful :)
Re:Cdecl: C Gibberish ↔ English by xscott on Saturday April 23, 2022 @04:41PM (#31138306)
My friend and I keep a link to the following because of our similarly neurotic tendencies :-)

https://github.com/timvieira/justified-variables