It is apparently quite easy to think that b
refers to something which it did earlier, when it actually refers to something newer than that.
This makes more sense as a problem if you rename it err
instead.
Only :
part is for declaration, so suffering a bit of lack of aesthetics, something like
var1, ok := foo1()
var2:, ok = foo2()
could work. But overall this is not a good idea.
This is hard to spot and relying on “multiple return values can only be declared altogether” removes some mental overhead when reading the code.
This syntax is ambiguous still. How do you declare the ok
but reuse the variable before? Your syntax cannot allow for it.
var, ok := foo()
var:, ok = foo()
var, ok: = foo() // same as the first one
var, ok: := foo() // ambiguous with `::` followed by `=`
There is a reason I said this twice:
I AM NOT LOOKING FOR SYNTAX SUGGESTIONS. PLEASE DO NOT GIVE ME THEM!
I thinking more and more that this idea has more cons than it has pros.
f, ok := foo()
b, ok := bar()
Why the second ‘ok’ is not allowed? The language allow shadow .
But what if reusing only the most right-hand variable was allowed? (Kinda the same exception to the rule as or_*
stuff)
The language allows showing in nested scopes NOT within the same scope.
Still no. I want things to work as expected.
I can see how this can be useful in some cases (like you pointed out), but I think it’s too much of a niche to be bothered.
I’d find this useful to catch an error and handle it but still benefit from defer if err != nil
semantics. It could be achieved by adding a keyword that sets the last return value like an or_return but doesn’t return so you can then do if err == .File_Not_Found
right after
To me, a lot of this thread seems to be an effort to avoid a bit of typing by adding a lot of complexity. The juice just isn’t worth the squeeze.
- Use different names for your error variables.
- If you don’t care what the error code is, use the “Ignore it!” underscore.
- You could maybe use one of the “or_” keywords to sidestep the entire issue.
foo_val, foo_ok := foo()
bar_val1, bar_ok1 := bar()
bar_val2, bar_ok2 := bar()
baz_val1, _ := baz()
baz_val2 := baz() or_else 42
I sometimes like to shadow a variable, just to make sure I don’t accidentally use the previous binding further down in my code.
One trick can be to do a new scope with {} but that only works situationally.
I would like a syntax to opt in to shadowing, not so much for the convenience of the err/ok, but for the above reason. And the new binding would then not have to conform to any previous type, as it is a new binding.
Hi! I’m a complete beginner with Odin, but what I’ve been doing to avoid name collisions is using scopes like this:
result1: V1
{
ok := false
result1, ok = proc_call1()
if !ok {
fmt.eprintln("Error 1 ...")
return
}
}
result2: V2
{
ok := false
result2, ok = proc_call2()
if !ok {
fmt.eprintln("Error 2 ...")
return
}
}
I like using small scopes to keep my namespace clean, since many variables have a very limited scope of use. I also think it helps visually separate the code nicely.
Maybe it would be nice to have something like this instead:
result: V = {
value, ok := proc_call()
if !ok {
fmt.eprintln("Error...")
return
}
break value // sort of return from the scope
}
I think it would be a bit cleaner — though I guess it would have many other implications for the language — but the first solution works well enough for me
Oh yes I would love to be able to “return” a value from a scope!
I also like scopes being expressions, like in Rust.
But I suppose that it is not really C-like enough for Odin, and is not giving any more semantic power than if you declared then assigned like you currently do. Except maybe having the infered type from the scope return type?
So, hypotetically running with that, you might want to do a meta-programming step to support “scopes as expressions” and see how it goes.
Maybe using some custom attribute for your meta-program parser to search and transform from:
( As Odin attributes are normally for declarations, the attribute is on the declared variable, but you can do your own syntax it’s only for demonstration purpose )
@init_from_scope result := {
value: V
// [...]
value
}
to:
result: V = ---
{
value : V
// [...]
result = value // sort of return from the scope
}
Anyway, I think that using scope for dealing with name collision is a good approach, even if the syntax is a bit more verbose than if we had “scopes as expressions”.
Have a great day everyone
The break value
syntax conflicts with labeled goto in Odin. You can accomplish the effect of the second block with a procedure though.
I wasn’t proposing any specific syntax when I mentioned break (it was just the first keyword that came to mind that wasn’t return). As far as I know, when using a nested procedure, variables from the outer scope aren’t accessible inside unless you pass them as parameters. That seems even less convenient if you need to use multiple variables from the outer context. Overall, as I said, using a scope without it being an expression isn’t really a problem, but in my opinion, having it as an expression would likely be a bit more ergonomic.
Scopes as expressions will NEVER be a thing in Odin. We experimented with it early on and found it was a very bad idea for the feel and general philosophy of Odin.
Dragging this up again, I’ll try to restate the pro case:
In short, sometimes I want to assign the values of a multi-value expression to a mix of new variables and existing variables (or other assignment targets, e.g. foo.bar). To get the benefit of type inference for the new variables, it would be nice to have syntax that let’s me suppress declaration on chosen assignment targets. The syntax could be something like:
// only c and e are new variables
// the rest are normal assignment targets, despite being in a := context
*a, *b.foo, c, *d, e := foo()
This is not variable “reuse” in the sense of shadowing or redeclaration: it’s just assigning to an existing thing.
Currently I would have to instead do:
c: Something
e: Something
a, b.foo, c, d, e = foo()
Or maybe instead:
a_, b_, c, d, e_ := foo()
a, b.foo, d = a_, b_, e_
The first pattern isn’t so bad because of the verbosity: rather, the annoyance is that I may have to stop and check the precise return types. If that sounds lazy, sure it is, but that’s what type inference is for. Honestly I’m a bit baffled by the suggestions in this thread that this allowance would be taking type inference too far.
Again, I don’t see how multi-value assignment to some new variables plus some existing ones is an anti-pattern. You only “lose information” in the sense that you are assigning to an existing variable and thus overwriting its prior value, but that’s just normal variable assignment. Nor do I see how type inference here creates a special readability problem anymore than the existing type inference does (none at all, imo).
I’m not sure what semantic rules you have in mind, but I don’t see how this isn’t easy for users to understand. Again, the * (or whatever symbol) should just mean, ‘This is a normal assignment with a normal assignment target, not an implicit declaration’. Instead of adding new magic, this simply adds an escape out of the existing := magic.
If the concern is that this encourages sloppiness, the one example given was about errors:
// in Go this is allowed, encouraging people to reuse
// the same err throughout a scope
a, err := foo()
b, err := bar()
// in Odin, you have to verbosely work around it
err: Some_Error
a: Apple
a, err = foo()
b: Banana
b, err = bar()
// therefore Odin encourages people to just
// pick different names...
a, foo_err := foo()
b, bar_err := bar()
//... which * would undermine
a, err := foo()
b, *err := bar()
And I guess the argument from there is that sloppily reused variables commonly lead to dumb mistakes and bad readability. I’d have more sympathy for this paternalistic argument if the language addressed more serious variable misuse accidents that can stem from shadowing:
This would break existing Odin code, but I’d actually prefer shadowed variable declarations to require explicit marking, e.g.
a: int
{
%a, b := 3, 5 // % acknowledges the shadowing
}
This idea is somewhat related to the * proposal because := assignment tends to make accidental shadowing more common. I’ve been burned more than a few times in Go with cases like this:
// often a mistake:
var a int
{
a, b := foo() // creates a new 'a' in this scope instead of using 'a' of outer scope
}
// the fix
var a int
{
var b *Bar
a, b = foo()
}
The ‘non-declaration assignment target marker’ would make the fix less verbose (and maybe more natural to write correctly in the first place), while the ‘explicit shadow declaration’ requirement would prevent such accidents.