Skip to content

Remove RNGScope usage for Rcpp exports #220

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Remove RNGScope usage for Rcpp exports #220

wants to merge 1 commit into from

Conversation

shikokuchuo
Copy link
Member

Closes #167.

This uses the Rcpp::export(rng = false) directive to opt out of Rcpp automatically adding RNGScope to our exported functions.

We do not make use of the R RNG, so this is safe to do, and is a 'free' boost to performance.

Crucially we still leave this RNGScope on the stack when evaluating all user callback code.

The original issue is solved:

.Random.seed <- NULL
library(later)
.Random.seed
#> NULL

Created on 2025-07-28 with reprex v2.1.1

I also ran a revdepcheck to be sure this doesn't affect downstream code.

@wch @jcheng5 fyi, pls shout if you see any problems with this.

@shikokuchuo shikokuchuo added this to the later 1.5.0 milestone Jul 28, 2025
@wch
Copy link
Member

wch commented Jul 28, 2025

If I understand this change correctly, I think it could lead to some surprising behavior. For example, imagine that you are at the console and you run these two commands line by line:

set.seed(123)

# Wait for a moment

runif(4)
#> [1] 0.2875775 0.7883051 0.4089769 0.8830174

You might reasonably expect that this would result in the same answers each time. But with the change in this PR, if any callbacks in the event loop use the RNG, they will alter this behavior.

Another similar situation is where the code calls explicitly runs the event loop, or calls a function that does so. In this case, it doesn't require an idle console for the callbacks to affect the RNG state. This case isn't as surprising/problematic as the idle console case I described above, but it may still be surprising when your code changes behavior based on something external that happens -- for example, an incoming message from the network could cause a callback to fire, which alters the state of the RNG.

set.seed(123)
run_now()
runif(4)

There may be implications of the behavior with promises, httpuv, and Shiny as well. (Although the latter two use Rcpp and the RNG state might be preserved because of that.)

@shikokuchuo
Copy link
Member Author

I'm sorry I should have emphasized this:

Crucially we still leave this RNGScope on the stack when evaluating all user callback code.

The behaviour of user code provided in callbacks would not change at all due to this PR.

The only change here is we don't 'initialize' the RNG state just by calling one of our own functions (which we do in our .onLoad().

We in later don't use the RNG at all, so there would have been no effect from instantiating RNGScope for our own functions. That's why even previously we wouldn't cause the .Random.seed value to change, but only for it to be initialized if it was NULL before, as you found out here: #167 (comment)

@shikokuchuo
Copy link
Member Author

shikokuchuo commented Jul 28, 2025

To remove one potential source of confusion: RNGScope doesn't preserve RNG state, rather it manages GetRNGstate and PutRNGstate (from the R internals). What these functions do is get the seed from the R .Random.seed object, then after random draws are made in C, ensures that the correct seed is written back to the R object.

See: https://github.com/RcppCore/Rcpp/blob/29b3b78df547e55dbcceb1c5e81a978f441dd58b/inst/include/Rcpp/RNGScope.h#L27-L31 and https://github.com/RcppCore/Rcpp/blob/29b3b78df547e55dbcceb1c5e81a978f441dd58b/src/api.cpp#L55-L67.

For our functions, there are no random draws, so it will always write back the same seed. (except that if there wasn't one before, then it instantiates one).

Hope that's clear.

@wch
Copy link
Member

wch commented Jul 28, 2025

Crucially we still leave this RNGScope on the stack when evaluating all user callback code.

Ah, OK, that's important -- sorry for overlooking that. If that's the case, then this PR shouldn't result in any changes to behavior, right? (Other than the issue in #167.) If that's true, then I am on board with this change.

@shikokuchuo
Copy link
Member Author

Yes, this should not result in any change in behaviour. Thanks for taking a look and kicking the tyres!

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

Successfully merging this pull request may close these issues.

REPRODUCIBILITY: .Random.seed is updated when 'later' is loaded
2 participants