It says in the manual that a mutable return value needs to be derived from the first argument of the proc.
What is strange that for the mgetOrPut proc, calling [] seems to count as deriving, but using a custom proc does not.
Here, it seems to be a problem that a copy of an immutable variable is to be placed in var foo.
# custom `[]` proc
import tables
type Foo = object
proc `[]`(f: Foo, key: string): string =
key
proc mCustomGet(f: var Foo, key: string): var string =
f[key]
var f = Foo()
var foo = f.mCustomGet # Error: expression has no address
But here, there is also a copy of a variable placed into var foo, but that's fine.
import tables
var t = initTable[string,string]()
var foo = t.mgetOrPut("foo", "bar")
How can I achieve the same effect in mCustomGet as in mgetOrPut?
you forgot to pass a value for key in your example, though that's not the reason this error appears (it's there even when you fix it).
To return f[key] as a var string, it needs to be an value which can be modified (an l value is what you would say in C land). But your [] proc returns just an immutable string, so you need to define a [] which gives a mutable value:
# custom `[]` proc
import tables
type Foo = object
x: string
proc `[]`(f: Foo, key: string): string =
key
proc `[]`(f: var Foo, key: string): var string =
f.x
proc mCustomGet(f: var Foo, key: string): var string =
f[key]
var f = Foo()
var foo = f.mCustomGet("aa")
Thanks for explaining!
Can you show me how to create such an l-value and return it? If I just define a mutable variable it resides on stack and cannot be returned.
well as you can see in the code I sent, the most common way is to pass in an object marked with var. Then both this object and all of it's members are guaranteed to survive this stackframe and can be returned again in mutable form.
This is e.g. also what Table does, where you pass in a var Table and it does a lookup based on the key to return a mutable access to it's own internal seq of values.
Please disregard the previous message, I'd overlooked something! But now I've got it.
Okay so my Eureka moment was that there have to be two different [] accessors, one for Foo and one for var Foo, and that's exactly what's happening in the tables module where there is one for Table and one for var Table.
That clears it up! Thanks!
The part I don't understand though- with mgetOrPut the manual goes to great lengths to explain that a mutable copy of the value is returned.
import tables
var t = initTable[string,string]()
var foo = t.mgetOrPut("foo", "bar")
foo = "fuz"
echo foo # prints bar, not fuz
So if the result of t.mgetOrPut gets copied into var foo anyway, why worry about the lifetime of foo["foo"] at all? Same with your modifier example:
var foo = f.mCustomGet("aa")
foo = "buz"
echo f.x # prints empty string
Without a borrow checker there is no way to ensure this safety so what Nim assumes you mean when you do var a = someVarGetter(myVal) is that you want to copy.
That is one reason, but the primary reason is consistency with everything else:
var x = a[i]
x = 4 # does it affect a[i]? No, it doesn't.
@ElegantBeef @Araq Thanks! That makes sense.
But if it's going to be a copy anyway, why can't I do this?
proc foo(): var string
return "foo"
var a = foo()
I know the direct answer is 'because it's not an l-expression', but my question is, why does it need to be an l-expression if it's going to get copied? Are there any cases where it matters what becomes of f.x after returning it?
Or on a related note- if I do this:
type Foo = object
x: string
proc getX(f: Foo): var string =
f.x
var f:foo
var a = f.getX()
Where does a's memory live? On the stack? In the same place f.x lives but newly allocated?
If I don't care what happens to f.x, I just want to return a random var string from a proc that also happens to take a var Foo as first parameter. Would it be safe to f.x as a kind of dummy variable that is only set in the proc and returned and reused at the next call? This would of course be a bit of a hack but better than worrying about keeping references to everything getX has ever returned around.
The point of my post was to point out var T copies if it's not directly used so
proc doThing(s: var (string, )): var string = s[0]
var a = ("hello", )
let b = a.doThing() # copies `"hello"` and assigns b to it
(a.doThing) = "world"
assert b != a[0]
var T can only exist in present Nim(without views) if directly used otherwise you want ptr T or ref T.
But if it's going to be a copy anyway, why can't I do this?
It is not going to be a copy anyway. You can pass the var T to some other var T:
inc tab[x] # mutates the value in `tab`!
# compare that with:
inc a[i]
The key to understanding here is that Nim is consistent, albeit in a not so familiar manner as other programming languages are not.
Okay I think I can see dawn... so if I were to simply use a dummy value, then I would be creating unpredictable behavior in those instances where the var T is passed to another function.
So the reason to return a var T from a table is to allow another proc to modify it.
But if I have a table-like object that does not support its memory to be modified directly, then I assume I probably shouldn't be using var T in the first place.
So unless I misunderstood something else here I think I'm clear.
Is there a place in the manual where the var T assignment creating the copy is explained? I couldn't find it but if pointed out I might be inclined to add a somewhat more verbose explanation.
But if I have a table-like object that does not support its memory to be modified directly, then I assume I probably shouldn't be using var T in the first place.
Correct, you can use lent T then in order to avoid the copy and yet keep the immutability.
Is there a place in the manual where the var T assignment creating the copy is explained? I couldn't find it but if pointed out I might be inclined to add a somewhat more verbose explanation.
I cannot find it either, should be part of
https://nim-lang.org/docs/manual.html#procedures-var-return-type
or maybe
https://nim-lang.org/docs/manual.html#statements-and-expressions-var-statement
Great, thanks for the lent tip I thought that required a switch to enable.
Suggested documentation is in this pull request, comments welcome.