Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map is empty if modified in arrays.each #20008

Closed
slavaschmidt opened this issue Nov 27, 2023 · 13 comments
Closed

Map is empty if modified in arrays.each #20008

slavaschmidt opened this issue Nov 27, 2023 · 13 comments

Comments

@slavaschmidt
Copy link

slavaschmidt commented Nov 27, 2023

Describe the bug

if used as a buffer, a map is empty if modified inside an arrays.each call but not empty if modified inside for

Reproduction Steps

following snippet returns empty buf:

 mut buf := map[string]u64{}
 arrays.each(some_array, fn [mut buf] (p Participant) {
  b := buf[p.participant_addr] or { 0 }
  buf[p.participant_addr] = b + p.amount
 }) 
println(buf)

Expected Behavior

a non-empty map is printed like it is done if arrays.each replaced with foris

Current Behavior

an empty map is printed

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.4.3 44cf145

Environment details (OS name and version, etc.)

Darwin 23.1.0 Darwin Kernel Version 23.1.0: Mon Oct 9 21:28:12 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T8103 arm64

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

@slavaschmidt slavaschmidt added the Bug This tag is applied to issues which reports bugs. label Nov 27, 2023
@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

It's not a bug.

The closure parameter is just a copy, and changes to it are only visible inside the anonymous function. If you pass the pointer, you can achieve the effect you want:

import arrays

fn main() {
	mut buf := 0
	mut ptr := &buf
	mut some_array := [1, 2, 3]

	arrays.each(some_array, fn [ptr] (e int) {
		unsafe { *ptr += e }
	}) 

	println(buf) // outputs: 6
}

@shove70 shove70 removed the Bug This tag is applied to issues which reports bugs. label Nov 27, 2023
@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

See the V documentation:
https://github.com/vlang/v/blob/master/doc/docs.md#closures

@slavaschmidt
Copy link
Author

Thanks
If used with reference, both lines

  b := buf[p.participant_addr] or { 0 }
  buf[p.participant_addr] = b + p.amount

produce warnings warning: pointer indexing is only allowed in unsafe blocks so an unsafe block is needed like in your example.
As a developer, I'm puzzled by this behavior because I'm not doing any pointer indexing, I'm just accessing a key of a map.

@slavaschmidt
Copy link
Author

As a side note, the decision to pass a copy of a variable to the closure is very confusing. Maybe I'm ignorant, but I don't know any language that would do this.

@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

map[this key]
array[this index]

They are both indexing behaviors

@slavaschmidt
Copy link
Author

but is it really pointer indexing?

@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

The wording of the warning is a bit general, you're right

@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

As a side note, the decision to pass a copy of a variable to the closure is very confusing. Maybe I'm ignorant, but I don't know any language that would do this.

python, ruby...

x = 10
def add_closure(y):
    x += y
print(x)     // 10

@slavaschmidt
Copy link
Author

slavaschmidt commented Nov 27, 2023

The wording of the warning is a bit general, you're right

for me it is really hard to understand why the lookup of a key in map or index in an array is an unsafe operation, if the structure is a pointer and safe if it is a value. the lookup happens after dereferencing the pointer I assume, so it should be the same process after that in both cases

@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

Indexing an array is essentially pointer arithmetic, also known as pointer indexing.

arr := [1, 2, 3...]
arr[index]:
arr is not a reference, and V guarantees that its internal pointer arithmetic is not based on null or dangling Pointers (except when subscript is out of bounds).

ptr := &arr
ptr[index]:
ptr is a reference. Is ptr a null pointer or a null dangling pointer? V can only ask the programmer to use unsafe to remind the programmer to make sure the pointer is valid

@slavaschmidt
Copy link
Author

Indexing an array is essentially pointer arithmetic, also known as pointer indexing.

arr := [1, 2, 3...] arr[index]: arr is not a reference, and V guarantees that its internal pointer arithmetic is not based on null or dangling Pointers (except when subscript is out of bounds).

ptr := &arr ptr[index]: ptr is a reference. Is ptr a null pointer or a null dangling pointer? V can only ask the programmer to use unsafe to remind the programmer to make sure the pointer is valid

This makes sense. From another point of view, maybe the compiler could infer that some operations that are commonly unsafe are indeed safe, for example:

mut buf := &map[string]u64{}
println(buf['a'] or {0})

I would argue that the code in my original example is also "safe" in this sense

@shove70
Copy link
Contributor

shove70 commented Nov 27, 2023

Ideally, this is true, but we can have more complex situations, such as:

import arrays

fn main() {
	mut buf := 0
	mut ptr := &buf
	mut some_array := [1, 2, 3]

	go/spawn  arrays.each(some_array, fn [ptr] (e int) {
		*ptr += e
	})

	println(buf)
 	ptr = unsafe { nil }
}

@slavaschmidt
Copy link
Author

perfectly clear. This is exactly why I mentioned "some operations" :D Thanks for the feedback. The discussion goes too far from the original issue. IMO, the confusion here is that in some languages, closures modify the value they close over. In other languages, they don't, so one needs to be very careful about these semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants