Here is a clue
julia> n = big"3"^100000;
julia> n.alloc
2494
julia> Base.GMP.MPZ.set!(BigInt(), n).alloc
2477
julia> copy(n).alloc
2494
Note that copy(n::BigInt) dispatches to copy(x::Number) = x. There must be a reason for this, even though ismutable(big(1)) is true.
EDIT: If you follow the code for Base.GMP.MPZ.set! you’ll find that it allocates memory for the target based on the size of the source, not the alloc of the source:
julia> n.alloc
2494
julia> n.size
2477
void
mpz_set (mpz_ptr w, mpz_srcptr u)
{
mp_ptr wp, up;
mp_size_t usize, size;
usize = SIZ(u);
size = ABS (usize);
wp = MPZ_NEWALLOC (w, size);
up = PTR(u);
MPN_COPY (wp, up, size);
SIZ(w) = usize;
}
EDIT: It’s really here
julia> Rational(n).num.alloc
2494
julia> Rational(n, 1).num.alloc
2477
The two-arg version calls set, which as mentioned above, uses size rather than alloc to determine what to allocate.
And the one-arg version calls Base.unsafe_rational, which does no copy at all. The numerator in the rational is the same object as n.