from the docs, seems like TaskLocalRNG has some distinct advantages like being reproducible in multithread environment. That’s why it’s chosen as the default.
That’s not right. There is only one task local RNG per task, so Random.seed!(Random.TaskLocalRNG(), 123) does not create a new RNG. It seeds the existing one. That’s also why there isn’t a Random.TaskLocalRNG(::Int64) method - you can’t make a new task local RNG.
seed!(x::Int) is equivalent to seed!(Random.TaskLocalRNG(), x::Int), except that the former also stores the used seed, such that it can be ‘reset’ later by functionality such as @testset.
The TaskLocalRNG is a zero-sized struct that refers to four of the five rngState fields in the existing task:
julia> dump(Task)
mutable struct Task <: Any
[... more fields ...]
rngState0::UInt64
rngState1::UInt64
rngState2::UInt64
rngState3::UInt64
rngState4::UInt64
[... more fields ...]
TIL. Apparently Random.TaskLocalRNG() always returns the same object:
julia> let
r1 = Random.TaskLocalRNG()
r2 = Random.TaskLocalRNG()
r1 === r2
end
true
So the object returned by Random.TaskLocalRNG() is always the same. That’s the first time I see a constructor that doesn’t return a new object…
As a side note, why not make Random.TaskLocalRNG(seed::Int) = Random.seed!(Random.TaskLocalRNG(), seed)? That would make the API equivalent to, say, Xoshiro and MersenneTwister.