""" LazyString <: AbstractString A lazy representation of string interpolation. This is useful when a string needs to be constructed in a context where performing the actual interpolation and string construction is unnecessary or undesirable (e.g. in error paths of functions). This type is designed to be cheap to construct at runtime, trying to offload as much work as possible to either the macro or later printing operations. # Examples ```jldoctest julia> n = 5; str = LazyString("n is ", n) "n is 5" ``` See also [`@lazy_str`](@ref). !!! compat "Julia 1.8" `LazyString` requires Julia 1.8 or later. # Extended help ## Safety properties for concurrent programs A lazy string itself does not introduce any concurrency problems even if it is printed in multiple Julia tasks. However, if `print` methods on a captured value can have a concurrency issue when invoked without synchronizations, printing the lazy string may cause an issue. Furthermore, the `print` methods on the captured values may be invoked multiple times, though only exactly one result will be returned. !!! compat "Julia 1.9" `LazyString` is safe in the above sense in Julia 1.9 and later. """ mutable struct LazyString <: AbstractString const parts::Tuple # Created on first access @atomic str::Union{String,Nothing} global _LazyString(parts, str) = new(parts, str) LazyString(args...) = new(args, nothing) end """ lazy"str" Create a [`LazyString`](@ref) using regular string interpolation syntax. Note that interpolations are *evaluated* at LazyString construction time, but *printing* is delayed until the first access to the string. See [`LazyString`](@ref) documentation for the safety properties for concurrent programs. # Examples ``` julia> n = 5; str = lazy"n is \$n" "n is 5" julia> typeof(str) LazyString ``` !!! compat "Julia 1.8" `lazy"str"` requires Julia 1.8 or later. """ macro lazy_str(text) parts = Any[] lastidx = idx = 1 while (idx = findnext('$', text, idx)) !== nothing lastidx < idx && push!(parts, text[lastidx:prevind(text, idx)]) idx += 1 expr, idx = Meta.parseatom(text, idx; filename=string(__source__.file)) push!(parts, esc(expr)) lastidx = idx end lastidx <= lastindex(text) && push!(parts, text[lastidx:end]) :(LazyString($(parts...))) end function String(l::LazyString) old = @atomic :acquire l.str old === nothing || return old str = sprint() do io for p in l.parts print(io, p) end end old, ok = @atomicreplace :acquire_release :acquire l.str nothing => str return ok ? str : (old::String) end hash(s::LazyString, h::UInt64) = hash(String(s), h) lastindex(s::LazyString) = lastindex(String(s)) iterate(s::LazyString) = iterate(String(s)) iterate(s::LazyString, i::Integer) = iterate(String(s), i) isequal(a::LazyString, b::LazyString) = isequal(String(a), String(b)) ==(a::LazyString, b::LazyString) = (String(a) == String(b)) ncodeunits(s::LazyString) = ncodeunits(String(s)) codeunit(s::LazyString) = codeunit(String(s)) codeunit(s::LazyString, i::Integer) = codeunit(String(s), i) isvalid(s::LazyString, i::Integer) = isvalid(String(s), i)