Which Lifetime & Type Sugar Is Cleaner And Easier To Read? Which One Is Best Way To Build Extension To Remove Semicolon?

I'm saying that with the current syntax, doSomething(); means that I don't intend the return value of the function to also be returned from the block and doSomething() means I do intend it to be returned. If the return type then differs from what that intent needs, I get an error.

In a syntax where the semicolon is inferred, I suppose I may instead have to add semicolons or explicit () lines where I want to prevent it from being returned. More of an opt-out than an opt-in to returning something. But in your pursuit, that may not be important.

As for the blog posts, I have nothing more to say if you aren't interested.

So are you saying you can do this?

let a = if 2 == 2 {
    2 * 4;
} else {
    6 * 2;
}

fn abc() -> i32 {
    2 * 6;
}

No. If you already declare a variable or return type, then supprisingly your intent is putting ; , you get compile error expected i32 value but not got it. So you will have to remove ; because you already say upfront I want a return value. You can only do that with the new never type

So it is not a problem. If what you want to say is different than what I get, please write quick pseudocode

type_curry!{? (index usize, hash String, hash String) -> i32}

// index usize (as in Index, IndexMut) makes a Vec<_>
// ordered T (as in Ord) makes a BTreeMap<T, _>
// hash T (as in Hash) makes a HashMap<T, _>

Tune to taste.

I just noticed that you like to omit the full stops and the end of paragraphs as well. For consistency you could also omit full stops ant the end of sentences within paragraphs as well. We can infer them from your use of capitalisation starting the new sentence.

Anyway, I like semi-colons in code. You know where you are when you have semicolons. Unlike the confusion that can happen in languages without them, like Javascript, Odin, Python.

That is confusing, what is that? Which one is parent, which one is child. How it encode deep hierarchy where childs also has multi siblings?

Unrelated comment again. I am searching related comment

You can do that since it is preserved, the compiler will not insert semicolon if it is already inserted. But, the majority programmer including from different language out there don't like it. Simplicity wins, that is why language that is clean has many user base even if it is young. Show the confusion that happen in language that do it properly, not picking the worse one just to make feel better. Like Kotlin, Swift, Golang. I never heard people say they are confused there, what I heard is this language is clean

I'm using 1 + 2 as a placeholder for something less obvious. Here's with mandatory semicolons:

fn example1() {
    1 + 2 // Error
}
fn example2() {
    1 + 2; // Ok
}
fn example3() -> i32 {
    1 + 2 // Ok
}
fn example4() -> i32 {
    1 + 2; // Error
}

Here's with inferred semicolons, if I understand you correctly:

fn example1() {
    1 + 2 // Ok, inserts ;? Or error from mismatch?
}
fn example2() {
    1 + 2; // Ok, already has ;
}
fn example3() -> i32 {
    1 + 2 // Ok
}
fn example4() -> i32 {
    1 + 2; // Error
}

The difference is small but example1 is where I think it matters what the rules prioritize. Does it error because the expression evaluates to something other than () or allow it and insert ; because the function should return ()?

I already said above multiple times that, in function :

  • if it is last code that is literal value, literal variable, or function call that return value, the semicolon is not inserted there

Multiple times I said that

fn example1() {
    1 + 2 // it is last value before function end, I already said last literal value and literal variable and function call that return that 2 is never be inserted semicolon. So the missmatch type will do its compile error, not expected return value but got return value integer
}
fn example2() {
    1 + 2; // Ok, already has ;
}
fn example3() -> i32 {
    1 + 2 // Ok
}
fn example4() -> i32 {
    1 + 2; // Error
}

Have you got that now?

@ogeon If you are confused of what I mean last literal value, last lteral variable, last function call that return them. Here is visualization

let a = if 2 == 2 {
    // some many code here
    2 * 3 // last literal value 
} else {
    // some many code here
    4    // last literal value
}

let a = if 2 == 2 {
    let b = 2
    let c = b * 3
    c // last literal variable
} else {
    let b = 4 
    let c = 5
    b * c // last literal value, b * c produces 20
}

fn a() -> i32 {
    // some many code here
    let b = 2
    2 * 4 // last literal value
}

let b = if 2 == 2 {
    // some many code here
    a() // last function call that return value
} else {
    // some many code here
    let c = 10
    a() * c // last literal value, a() returns 8, 8 * 10 = 80, or you call it last operation that produces value
}

I think I get what you mean and I may have missed or misunderstood that piece of information. With that established, here's the wrinkle in the semantics I had in mind. Now, let me know if I still got it wrong, I think we use different terminology sometimes.

If we have this case:

fn do_something() {
    // Process items
}

fn example() {
    do_something()
}

This is allowed with both mandatory and inferred semicolons. It's the last expression but the return types match. The compiler doesn't complain.

If do_something is later changed to return a value to report back what it did, for example the number of processed items:

fn do_something() -> usize {
    // Process items
    processed_item_count // Some callers want this, some don't care
}

fn example() {
    do_something() // Oops!
}

Now, this also errors in both versions (right?), but the implication is slightly different.

With mandatory semicolons, this is a clear mistake in example, as far as the compiler can tell. There wouldn't have been an error if the author didn't forget to add the semicolon to say that nothing should be returned. Clear as day, according to those rules.

With inferred semicolons, skipping the semicolon doesn't necessarily mean anything specific. It was left out because it's easier to not add it and it may look nicer. The return types matched before so all was good. However, now when they don't match, the author has to go and add a semicolon to "opt out" from returning, even if no mistake was initially made.

This is the scenario where I meant that one may eventually start to add a "defensive" semicolons at the end, regardless of whether it's needed or not. This may be a necessary and acceptable cost, depending who you ask.

Comparing to other languages, if you also would make the return keyword mandatory, we would have a new signal of intent and it would circumvent this specific situation. Nested blocks could still have more wrinkles, but they would likely also be more convoluted.

What do you mean by this?

fn do_something() {
    // Process items
    processed_item_count // Some callers want this, some don't care
}

fn example() {
    do_something() // Oops!
}

Do something does not has return type, the miss match return type handler would caught that and return compile error. So I'm not sure what do you mean

@ogeon Ah you wrote unmatched code with the wording and now fixed so. So what do you mean is this

fn do_something() -> usize {
    // Process items
    processed_item_count // Some callers want this, some don't care
}

fn example() { // the compiler see clearly that this function expects no return value
    do_something() // so semicolon is inserted here
}

Sorry, that was ironically enough a typo and I didn't correct it in time. :sweat_smile: It should return usize.

I just noticed that, and provided the answer for the corrected one

@ogeon After rereading again, so do you mean in simple word, how to tell if I have function that return value, I call it inside function that does not has return type, and I call it at the latest before function end, how to tell that I want to call said function?

It has no problem, the compiler see the parent function has no return type, so it can make decision that the semicolon will be inserted on the latest code. The no semicolon is only if returning value, aka only happen in function that has return type

Wait, didn't you say that it doesn't insert semicolons after the last expression, and error if it evaluates to a value? How can the compiler know what the author intended? Or do we get a special rule for function calls?

My point is that theres no need to guess what the author means of semicolons are mandatory. There will not be an error in this case when the author remembers to insert semicolons where they should be. And that would likely have happened if they were mandatory, as it becomes a habit.

Here's how it would more likely have looked in today's syntax, if the author didn't make a mistake:

fn do_something() -> usize {
    // Process items
    processed_item_count // Some callers want this, some don't care
}

fn example() {
    do_something(); // No problem!
}

With optional semicolons, it's not possible to say if the lack of a semicolon was the mistake or if example should have been changed to return usize. And the author is more incentivised to end up in this situation than to avoid it, as no semicolon is the norm.

As I said, this may be an acceptable trade-off and that would be a design choice to allow it to happen.

I said the no insert semicolon if it expect return value

let a: i32 = expects return value
fn a() { .. } no return type there, of course that does not in the category that expects return value, the compiler insert semicolon
fn a() -> i32 { .. } clearly that expects return value, no semicolon is inserted

To make you understand it even easier, here is a working quick demonstration of the function part. Uncomment the latest input code to see the common compile error if function that does not return value is called in the last code of function that expects return value

NOTE : this is just demonstration to help explain it using visualization

Then try this to see semicolon will be added because clearly the function expects no return value

fn no_return_type_2() {
    let c = 2
    return_i32()
}

You wrote this before:

which I interpreted as no semicolon inserted. Trying this in your example playground does insert a semicolon (assuming this case is covered).

If it does insert semicolons, it's instead more permissive. Again, possibly an acceptable trade-off and maybe less of a pitfall than the opposite. These tweaks and choices are what I meant you may need to make when changing the syntax. Even if you think the option you chose is obvious.

Yeah I made code error, but what I meant by function that returns value is function that has return type because can't return value if it doesn't has return type

It is clearly covered, not may or assuming again. I wanted to show that its all has clear determistic pattern, not something so random. So an algorithm can be built to automate it

Any other question?