It's more complicated that that. Yes, the immediate issue is that all non-__block
captured variables are const
inside the block. Therefore, inside the block cx
has type JSContext * const
and successCb
has type const jsval
. And const jsval *
cannot be passed to jsval *
. But you have to first understand why the variables are const
.
Blocks capture non-__block
variables by value at the time that they are created. That means the copy of the variable inside the block and the copy outside are different, independent variables, even though they have the same name. If it were not const
, you might be tempted to change the variable inside the block and expect it to change outside, but it does not. (Of course, the opposite problem still happens -- you can still change the variable outside the block, since it's not const
, and wonder why it does not change inside the block.) __block
resolves this issue by making it so there's only one copy of the variable, that is shared between the inside and outside of the block.
Then it's important to think about why a const
variable is not sufficient. If you just need the value of the variable, then a const
copy is just as well. When const
won't work, usually it's because of the need to assign to the variable. We need to ask, what does JS_RemoveValueRoot
that it requires a non-const
pointer to the variable? Is it to assign to the variable? (And if it does, do we care about the new value outside the block? Because if not, we can just assign the const
variable to a non-const
variable inside the block.)
It turns out it's more complicated. According to the documentation of JS_Remove*Root
, it neither uses the value of the variable pointed to, nor needs to set the variable; rather, it needs the address of the variable, and this needs to match the address passed to JS_Add*Root
. (Actually, I am not even sure whether a const
pointer is even needed for what they're doing.) I am assuming that JS_AddValueRoot
was done in the body of the function that encloses the block, outside the block. (I assume this since you said successCb
is a local variable, so it must be within this function; if it were within the block, it wouldn't make sense because then successCb
could just be a local variable of the block, and thus not need to be captured.)
Because the address of the variable itself is significant, let us consider what happens in the various block variable capture modes. A non-__block
variable is now clearly not appropriate, since there are two separate copies (and thus two separate addresses) for the inside and outside. Thus, the addresses given to Add
and Remove
won't match. A __block
variable is shared, and is much better.
However, there is still an issue with __block
variables that may make it not match -- the address of a __block
variable may change over time! This goes into the specifics of how blocks are implemented. In the current implementation, a __block
variable is held in a special structure (a kind of an "object") that starts out on the stack, but when any block capturing it is copied, it is "moved" to the heap as a dynamically-allocated structure. This is very similar to how block objects that capture variables start out on the stack, but is moved to the heap upon being copied. Putting it on the stack first is an optimization, and is not guaranteed to happen; but currently it does. The __block
variable itself is actually an access of the variable inside this structure, accessed through a pointer that tracks where this structure is. As the structure is moved from the stack to the heap, you can see the value of the expression &successCb
change. (This is not possible in normal C.) Therefore, to have matching addresses, you must ensure that the move has already occurred when you pass the address of the variable to Add
. You may be able to do this by forcibly copying a block that captures it.