The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
When does ast2ast duplicate your data, and when do writes inside a function stay visible to the caller? It depends on which boundary the argument crosses – and the same keyword can mean different things on each.
Base R never faces this question: vectors and lists are
copy-on-modify, so a change a function makes to its argument never
escapes. The one keyword that changes that – ref – has no
counterpart in ordinary R, so it is worth being precise about where it
applies.
The argument arrives as a SEXP (an ordinary R object)
and is turned into an etr vector before the body runs.
SEXP contents land in
f’s own vector at a new address. Your R vector is unchanged
after the call.
f’s vector points straight into
R’s SEXP buffer – same address, no copy. Without
const(), a write here reaches the caller’s storage.
scale_inplace <- function(v, k) {
argtypes(
v |> type(borrow_vec(double)),
k |> type(double)
)
for (i in seq_len(length(v))) {
v[[i]] <- v[[i]] * k
}
return(v)
}
f <- ast2ast::translate(scale_inplace)
x <- c(1, 2, 3)
f(x, 10)
x # c(10, 20, 30): x itself was modified, because f borrowed its buffer
Drop the borrow_vec – plain
v |> type(vec(double)) – and x stays
c(1, 2, 3): the function worked on a copy.
The argument is already an etr object, not a
SEXP – so ref() works here whatever
output you translate to. In this sense inner
fn() arguments behave like XPtr arguments.
g gets its own
copy of the etr object. Whatever g writes
stays in g.
g works on the caller’s object
itself. After g returns, the changes are still there – the
one place ast2ast behaves unlike base R.
demo <- function(v) {
argtypes(v |> type(vec(double)))
double_it <- fn(
argtypes(out |> type(vec(double)) |> ref()),
return(void),
{
for (i in seq_len(length(out))) {
out[[i]] <- out[[i]] * 2
}
}
)
double_it(v) # v is filled in place -- no copy, no allocation
return(v)
}
f <- ast2ast::translate(demo)
f(c(1, 2, 3)) # c(2, 4, 6)
Without |> ref() on out,
double_it would double a private copy and v
would come back unchanged. This is the mechanism behind the “preallocate
a buffer once, let an inner function fill it every iteration” pattern –
it removes a per-call allocation from hot loops.
noteref() on an argument of
the outer function is ignored, with a warning. Converting R’s
SEXP into an etr object has already happened,
so there is no shared handle left to pass by reference. Use
borrow_vec() / borrow_mat() to skip the copy
at that boundary.
| Keyword | Boundary | Copies the data | Writes visible to caller |
|---|---|---|---|
| (default) | R → translated f |
yes | no |
borrow_vec() / borrow_mat() |
R → translated f |
no | yes † |
| (default) | f → inner fn() |
yes | no |
\|> ref() |
f → inner fn() |
no | yes |
\|> ref() |
R → translated f |
-- ignored, warning -- |
† unless the argument is also marked const().
borrow = don’t copy the data. ref = don’t copy the object.
The observable rule, without pointers or SEXP in it: a
copied argument’s changes stop at the function; a
shared argument’s changes are still there after it returns. The
addresses in the figures are shortened from a real run of
vec <- c(9, 5, 6) – 0x...d2b0 is its
storage, 0x...22b0 a fresh allocation.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.