Skip to content

Commit 925d504

Browse files
Add takestring!(x) to create a string from the content of x, emptying it (#54372)
2 parents 2bd37fb + 6cfa52f commit 925d504

File tree

39 files changed

+220
-78
lines changed

39 files changed

+220
-78
lines changed

Compiler/src/ssair/show.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ function compute_ir_line_annotations(code::IRCode)
328328
loc_method = string(" "^printing_depth, loc_method)
329329
last_stack = stack
330330
end
331-
push!(loc_annotations, String(take!(buf)))
331+
push!(loc_annotations, takestring!(buf))
332332
push!(loc_lineno, (lineno != 0 && lineno != last_lineno) ? string(lineno) : "")
333333
push!(loc_methods, loc_method)
334334
(lineno != 0) && (last_lineno = lineno)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ New library features
5050
* `fieldoffset` now also accepts the field name as a symbol as `fieldtype` already did ([#58100]).
5151
* `sort(keys(::Dict))` and `sort(values(::Dict))` now automatically collect, they previously threw ([#56978]).
5252
* `Base.AbstractOneTo` is added as a supertype of one-based axes, with `Base.OneTo` as its subtype ([#56902]).
53+
* `takestring!(::IOBuffer)` removes the content from the buffer, returning the content as a `String`.
5354

5455
Standard library changes
5556
------------------------

base/errorshow.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ function showerror(io::IO, ex::MethodError)
307307
iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
308308
show_signature_function(iob, Core.Typeof(f))
309309
show_tuple_as_call(iob, :function, arg_types; hasfirst=false, kwargs = isempty(kwargs) ? nothing : kwargs)
310-
str = String(take!(buf))
310+
str = takestring!(buf)
311311
str = type_limited_string_from_context(io, str)
312312
print(io, str)
313313
end
@@ -598,7 +598,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
598598
m = parentmodule_before_main(method)
599599
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
600600
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)
601-
push!(lines, String(take!(buf)))
601+
push!(lines, takestring!(buf))
602602
push!(line_score, -(right_matches * 2 + (length(arg_types_param) < 2 ? 1 : 0)))
603603
end
604604
end

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ export
682682
split,
683683
string,
684684
strip,
685+
takestring!,
685686
textwidth,
686687
thisind,
687688
titlecase,

base/filesystem.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ import .Base:
141141
bytesavailable, position, read, read!, readbytes!, readavailable, seek, seekend, show,
142142
skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error, _uv_error,
143143
setup_stdio, rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize,
144-
isexecutable, isreadable, iswritable, MutableDenseArrayType, truncate
144+
isexecutable, isreadable, iswritable, MutableDenseArrayType, truncate, unsafe_takestring!
145145

146146
import .Base.RefValue
147147

base/indices.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function throw_promote_shape_mismatch(a::Tuple, b::Union{Nothing,Tuple}, i = not
132132
if i nothing
133133
print(msg, ", mismatch at dim ", i)
134134
end
135-
throw(DimensionMismatch(String(take!(msg))))
135+
throw(DimensionMismatch(takestring!(msg)))
136136
end
137137

138138
function promote_shape(a::Tuple{Int,}, b::Tuple{Int,})

base/int.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ macro big_str(s::String)
722722
is_prev_dot = (c == '.')
723723
end
724724
print(bf, s[end])
725-
s = String(take!(bf))
725+
s = unsafe_takestring!(bf)
726726
end
727727
n = tryparse(BigInt, s)
728728
n === nothing || return n

base/io.jl

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,13 @@ julia> io = IOBuffer();
277277
julia> write(io, "JuliaLang is a GitHub organization.", " It has many members.")
278278
56
279279
280-
julia> String(take!(io))
280+
julia> takestring!(io)
281281
"JuliaLang is a GitHub organization. It has many members."
282282
283283
julia> write(io, "Sometimes those members") + write(io, " write documentation.")
284284
44
285285
286-
julia> String(take!(io))
286+
julia> takestring!(io)
287287
"Sometimes those members write documentation."
288288
```
289289
User-defined plain-data types without `write` methods can be written when wrapped in a `Ref`:
@@ -544,7 +544,7 @@ julia> rm("my_file.txt")
544544
"""
545545
readuntil(filename::AbstractString, delim; kw...) = open(io->readuntil(io, delim; kw...), convert(String, filename)::String)
546546
readuntil(stream::IO, delim::UInt8; kw...) = _unsafe_take!(copyuntil(IOBuffer(sizehint=16), stream, delim; kw...))
547-
readuntil(stream::IO, delim::Union{AbstractChar, AbstractString}; kw...) = String(_unsafe_take!(copyuntil(IOBuffer(sizehint=16), stream, delim; kw...)))
547+
readuntil(stream::IO, delim::Union{AbstractChar, AbstractString}; kw...) = takestring!(copyuntil(IOBuffer(sizehint=16), stream, delim; kw...))
548548
readuntil(stream::IO, delim::T; keep::Bool=false) where T = _copyuntil(Vector{T}(), stream, delim, keep)
549549

550550

@@ -566,10 +566,10 @@ Similar to [`readuntil`](@ref), which returns a `String`; in contrast,
566566
```jldoctest
567567
julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n");
568568
569-
julia> String(take!(copyuntil(IOBuffer(), "my_file.txt", 'L')))
569+
julia> takestring!(copyuntil(IOBuffer(), "my_file.txt", 'L'))
570570
"Julia"
571571
572-
julia> String(take!(copyuntil(IOBuffer(), "my_file.txt", '.', keep = true)))
572+
julia> takestring!(copyuntil(IOBuffer(), "my_file.txt", '.', keep = true))
573573
"JuliaLang is a GitHub organization."
574574
575575
julia> rm("my_file.txt")
@@ -616,8 +616,7 @@ Logan
616616
"""
617617
readline(filename::AbstractString; keep::Bool=false) =
618618
open(io -> readline(io; keep), filename)
619-
readline(s::IO=stdin; keep::Bool=false) =
620-
String(_unsafe_take!(copyline(IOBuffer(sizehint=16), s; keep)))
619+
readline(s::IO=stdin; keep::Bool=false) = takestring!(copyline(IOBuffer(sizehint=16), s; keep))
621620

622621
"""
623622
copyline(out::IO, io::IO=stdin; keep::Bool=false)
@@ -642,10 +641,10 @@ See also [`copyuntil`](@ref) for reading until more general delimiters.
642641
```jldoctest
643642
julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n");
644643
645-
julia> String(take!(copyline(IOBuffer(), "my_file.txt")))
644+
julia> takestring!(copyline(IOBuffer(), "my_file.txt"))
646645
"JuliaLang is a GitHub organization."
647646
648-
julia> String(take!(copyline(IOBuffer(), "my_file.txt", keep=true)))
647+
julia> takestring!(copyline(IOBuffer(), "my_file.txt", keep=true))
649648
"JuliaLang is a GitHub organization.\\n"
650649
651650
julia> rm("my_file.txt")
@@ -1290,7 +1289,7 @@ function iterate(r::Iterators.Reverse{<:EachLine}, state)
12901289
buf.size = _stripnewline(r.itr.keep, buf.size, buf.data)
12911290
empty!(chunks) # will cause next iteration to terminate
12921291
seekend(r.itr.stream) # reposition to end of stream for isdone
1293-
s = String(_unsafe_take!(buf))
1292+
s = unsafe_takestring!(buf)
12941293
else
12951294
# extract the string from chunks[ichunk][inewline+1] to chunks[jchunk][jnewline]
12961295
if ichunk == jchunk # common case: current and previous newline in same chunk
@@ -1307,7 +1306,7 @@ function iterate(r::Iterators.Reverse{<:EachLine}, state)
13071306
end
13081307
write(buf, view(chunks[jchunk], 1:jnewline))
13091308
buf.size = _stripnewline(r.itr.keep, buf.size, buf.data)
1310-
s = String(_unsafe_take!(buf))
1309+
s = unsafe_takestring!(buf)
13111310

13121311
# overwrite obsolete chunks (ichunk+1:jchunk)
13131312
i = jchunk

base/iobuffer.jl

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ julia> io = IOBuffer();
198198
julia> write(io, "JuliaLang is a GitHub organization.", " It has many members.")
199199
56
200200
201-
julia> String(take!(io))
201+
julia> takestring!(io)
202202
"JuliaLang is a GitHub organization. It has many members."
203203
204204
julia> io = IOBuffer(b"JuliaLang is a GitHub organization.")
@@ -216,7 +216,7 @@ IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=fa
216216
julia> write(io, "JuliaLang is a GitHub organization.")
217217
34
218218
219-
julia> String(take!(io))
219+
julia> takestring!(io)
220220
"JuliaLang is a GitHub organization"
221221
222222
julia> length(read(IOBuffer(b"data", read=true, truncate=false)))
@@ -783,6 +783,80 @@ function take!(io::IOBuffer)
783783
return data
784784
end
785785

786+
"Internal method. This method can be faster than takestring!, because it does not
787+
reset the buffer to a usable state, and it does not check for io.reinit.
788+
Using the buffer after calling unsafe_takestring! may cause undefined behaviour.
789+
This function is meant to be used when the buffer is only used as a temporary
790+
string builder, which is discarded after the string is built."
791+
function unsafe_takestring!(io::IOBuffer)
792+
used_span = get_used_span(io)
793+
nbytes = length(used_span)
794+
from = first(used_span)
795+
isempty(used_span) && return ""
796+
# The C function can only copy from the start of the memory.
797+
# Fortunately, in most cases, the offset will be zero.
798+
return if isone(from)
799+
ccall(:jl_genericmemory_to_string, Ref{String}, (Any, Int), io.data, nbytes)
800+
else
801+
mem = StringMemory(nbytes % UInt)
802+
unsafe_copyto!(mem, 1, io.data, from, nbytes)
803+
unsafe_takestring(mem)
804+
end
805+
end
806+
807+
"""
808+
takestring!(io::IOBuffer) -> String
809+
810+
Return the content of `io` as a `String`, resetting the buffer to its initial
811+
state.
812+
This is preferred over calling `String(take!(io))` to create a string from
813+
an `IOBuffer`.
814+
815+
# Examples
816+
```jldoctest
817+
julia> io = IOBuffer();
818+
819+
julia> write(io, [0x61, 0x62, 0x63]);
820+
821+
julia> s = takestring!(io)
822+
"abc"
823+
824+
julia> isempty(take!(io)) # io is now empty
825+
true
826+
```
827+
828+
!!! compat "Julia 1.13"
829+
This function requires at least Julia 1.13.
830+
"""
831+
function takestring!(io::IOBuffer)
832+
# If the buffer has been used up and needs to be replaced, there are no bytes, and
833+
# we can return an empty string without interacting with the buffer at all.
834+
io.reinit && return ""
835+
836+
# If the iobuffer is writable, taking will remove the buffer from `io`.
837+
# So, we reset the iobuffer, and directly unsafe takestring.
838+
return if io.writable
839+
s = unsafe_takestring!(io)
840+
io.reinit = true
841+
io.mark = -1
842+
io.ptr = 1
843+
io.size = 0
844+
io.offset_or_compacted = 0
845+
s
846+
else
847+
# If the buffer is not writable, taking will NOT remove the buffer,
848+
# so if we just converted the buffer to a string, garbage collecting
849+
# the string would free the memory underneath the iobuffer
850+
used_span = get_used_span(io)
851+
mem = StringMemory(length(used_span))
852+
unsafe_copyto!(mem, 1, io.data, first(used_span), length(used_span))
853+
unsafe_takestring(mem)
854+
end
855+
end
856+
857+
# Fallback methods
858+
takestring!(io::GenericIOBuffer) = String(take!(io))
859+
786860
"""
787861
_unsafe_take!(io::IOBuffer)
788862

base/iostream.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ julia> write(io, "JuliaLang is a GitHub organization.")
111111
julia> truncate(io, 15)
112112
IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=false, size=15, maxsize=Inf, ptr=16, mark=-1)
113113
114-
julia> String(take!(io))
114+
julia> takestring!(io)
115115
"JuliaLang is a "
116116
117117
julia> io = IOBuffer();
@@ -120,7 +120,7 @@ julia> write(io, "JuliaLang is a GitHub organization.");
120120
121121
julia> truncate(io, 40);
122122
123-
julia> String(take!(io))
123+
julia> takestring!(io)
124124
"JuliaLang is a GitHub organization.\\0\\0\\0\\0\\0"
125125
```
126126
"""
@@ -469,7 +469,7 @@ function readuntil_string(s::IOStream, delim::UInt8, keep::Bool)
469469
end
470470
readuntil(s::IOStream, delim::AbstractChar; keep::Bool=false) =
471471
isascii(delim) ? readuntil_string(s, delim % UInt8, keep) :
472-
String(_unsafe_take!(copyuntil(IOBuffer(sizehint=70), s, delim; keep)))
472+
takestring!(copyuntil(IOBuffer(sizehint=70), s, delim; keep))
473473

474474
function readline(s::IOStream; keep::Bool=false)
475475
@_lock_ios s ccall(:jl_readuntil, Ref{String}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, '\n', 1, keep ? 0 : 2)

base/logging/ConsoleLogger.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module
147147
for (key, val) in kwargs
148148
key === :maxlog && continue
149149
showvalue(valio, val)
150-
vallines = split(String(take!(valbuf)), '\n')
150+
vallines = split(takestring!(valbuf), '\n')
151151
if length(vallines) == 1
152152
push!(msglines, (indent=2, msg=SubString("$key = $(vallines[1])")))
153153
else

base/pkgid.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function binpack(pkg::PkgId)
3232
uuid = pkg.uuid
3333
write(io, uuid === nothing ? UInt128(0) : UInt128(uuid))
3434
write(io, pkg.name)
35-
return String(take!(io))
35+
return unsafe_takestring!(io)
3636
end
3737

3838
function binunpack(s::String)

base/show.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,12 @@ julia> io = IOBuffer();
390390
391391
julia> printstyled(IOContext(io, :color => true), "string", color=:red)
392392
393-
julia> String(take!(io))
393+
julia> takestring!(io)
394394
"\\e[31mstring\\e[39m"
395395
396396
julia> printstyled(io, "string", color=:red)
397397
398-
julia> String(take!(io))
398+
julia> takestring!(io)
399399
"string"
400400
```
401401
@@ -2649,7 +2649,7 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
26492649
end
26502650
print_within_stacktrace(io, ")", bold=true)
26512651
show_method_params(io, tv)
2652-
str = String(take!(buf))
2652+
str = takestring!(buf)
26532653
str = type_limited_string_from_context(out, str)
26542654
print(out, str)
26552655
nothing
@@ -2758,7 +2758,7 @@ function type_depth_limit(str::String, n::Int; maxdepth = nothing)
27582758
end
27592759
prev = di
27602760
end
2761-
return String(take!(output))
2761+
return unsafe_takestring!(output)
27622762
end
27632763

27642764
function print_type_bicolor(io, type; kwargs...)
@@ -3193,7 +3193,7 @@ summary(io::IO, x) = print(io, typeof(x))
31933193
function summary(x)
31943194
io = IOBuffer()
31953195
summary(io, x)
3196-
String(take!(io))
3196+
takestring!(io)
31973197
end
31983198

31993199
## `summary` for AbstractArrays

base/stat.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ function filemode_string(mode)
320320
end
321321
complete && write(str, "-")
322322
end
323-
return String(take!(str))
323+
return unsafe_takestring!(str)
324324
end
325325

326326
"""

base/strings/annotated.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ function annotatedstring(xs...)
272272
print(s, x)
273273
end
274274
end
275-
str = String(take!(buf))
275+
str = takestring!(buf)
276276
AnnotatedString(str, annotations)
277277
end
278278

@@ -457,7 +457,7 @@ function annotated_chartransform(f::Function, str::AnnotatedString, state=nothin
457457
stop_offset = last(offsets[findlast(<=(stop) first, offsets)::Int])
458458
push!(annots, setindex(annot, (start + start_offset):(stop + stop_offset), :region))
459459
end
460-
AnnotatedString(String(take!(outstr)), annots)
460+
AnnotatedString(takestring!(outstr), annots)
461461
end
462462

463463
struct RegionIterator{S <: AbstractString}

base/strings/basic.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ function filter(f, s::AbstractString)
678678
for c in s
679679
f(c) && write(out, c)
680680
end
681-
String(_unsafe_take!(out))
681+
takestring!(out)
682682
end
683683

684684
## string first and last ##

0 commit comments

Comments
 (0)