Skip to content

[SUGGESTION] Statement-expressions, result vs return #391

Closed
@msadeqhe

Description

@msadeqhe

Preface

Statement-expression is a solution to have a group of statements in place of an expression.

I suggest to have statement-expressions in Cpp2 with syntax : = { ... } and the result of them are returned with result keyword. In this way, the following code:

call(: = { n: = num(); result n + 0; },
     : = { n: = num(); result n + 1; });

... is somehow equal to the following code except that we don't have to specify the type of arguments:

arg0: int; { n: = num(); arg0 = n + 0; }
arg1: int; { n: = num(); arg1 = n + 1; }
call(arg0, arg1);

Statement-expressions can be used to declare variables too:

// The type of `variable` is `int`.
variable: = {
    value: int;
    //: statements...
    result value;
}

Suggestion Detail

Currently we declare functions in Cpp2 in the following syntax:

// function or lambda style
func0: (param) = {
    return param;
}

// expression style
func1: (param) = param;

In a similar syntax, we can declare variables in Cpp2 with statement-expressions:

// statement-expression style
var0: = {
    result 0;
}

// expression style
var1: = 0;

Statement-expressions are unnamed variables in a similar manner that lambdas are unnamed functions:

  • We have to use result instead of return within statement-expressions.
  • We can use a statement-expression everywhere that an expression is required.
  • A declaration within their { ... } has the lifetime of that scope.
  • Their { ... } will be executed one time in their place.
  • They don't create a new function scope, therefore variables from outer scope don't have to be captured within their { ... }.

Statement-expressions will be not allowed to be used in place of statements. Because it's an error to have an unused value (literal or identifier) in Cpp2:

main: () = {
    // ERROR! It has to be in place of an expression not an statement.
    : = {
        x: = 0;
        result x + 1;
    }
    // ERROR! Because `x + 1` is an unused value (literal or identifier).
}

Expressions, Functions and Blocks

Now { ... } has different meanings in the following categories:

  1. : Type = { ... } is a statement-expression in which Type can be omitted and deduced from { ... }.
    • The execution of statements in { ... } ends with result something;.
    • It can be a part of variable declaration, or in place of an expression (e.g. function argument):
    x0: = 0;
    x1: = { result 0; }
    call(: = { result 0; })
  2. : (params) -> Type = { ... } is a function or a lambda in which -> Type can be omitted if the return type is void.
    • The execution of statements in { ... } ends with return something; or the end of }.
    • It can be a part of function declaration, or in place of an expression (e.g. function argument):
    x0: (param) = param;
    x1: (param) = { return param; }
    call(: (param) = { return param; })
  3. (params) { ... } is a parameterized statement block. Whereas { ... } is a statement block without (params) in which () must be omitted.
    • The execution of statements in { ... } ends with:
      • ... the end of }.
      • ... break and continue in loop control structures.
      • ... result something; when their outer scope is a statement-expression.
      • ... return something; when their outer scope is a function or a lambda.
    • It can be a part of control structures:
    for items do (item) { call(item); }
    if condition { call(); }

In all of the above categories, they have this similarity:

  • A declaration within their { ... } has the lifetime of that scope.

Additinally they have different behaviours in statement execution and variable capturing:

  • Statement-expressions (case 1) have to execute their { ... } one time in their place.
    • But other cases can execute their { ... } multiple times in any place.
  • Functions and lambdas (case 2) create a new function scope therefore variables from outer scope have to be captured within their { ... }.
    • But other cases don't create a new function scope, therefore variables from outer scope don't have to be captured within their { ... }.

I should mention that statement-expressions, functions and lambdas, statement blocks and parameterized statement blocks can be nested inside each other.

Your Questions

Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?

Yes. In a way that it helps Xto organize code and separate related statements in nested blocks to easily fix or prevent unwanted bugs which are a result of having mixed unrelated statements together.

Will your feature suggestion automate or eliminate X% of current C++ guidance literature?

Yes. In the following ways:

  • It will make the declaration syntax of variables and functions to be similar and consistent.
  • It helps programmers to separate related statements easily without defining extra variables.
  • It allows programmers to use if, while and other control structures as an expression that is more readable in variable assignment, because the intention is clearly written in code, see the example below.
  • It allows programmers to be less specific about types in generic programming, see the example below.

Consider how the following code:

variable: = {
    if condition {
        result hello();
    }
    else {
        result bye();
    }
}

... is more readable and generic than the following code which we have to also specify the type of the variable:

variable: int;

if condition {
    variable: = hello();
}
else {
    variable: = bye();
}

Considered Alternatives

Instead of result keyword, it's possible to use symbols such as => or nothing (like in Rust). For example:

variable: = {
    if condition {
        => hello();
    }
    else {
        => bye();
    }
}

... or this one (like in Rust):

variable: = {
    if condition {
        hello() // without ;
    }
    else {
        bye() // without ;
    }
}

Howerver I think that having a keyword like result or a notation like =>, is more readable than having nothing.

References

This suggestion is inspired from discussions in this issue and this paper mentioned by @JohelEGP and this informative comment from @hsutter which describes Unifying Functions and Blocks.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions