StaticStrings.jl v0.3, move to JuliaStrings

StaticStrings v0.3.0

StaticStrings.jl v0.3 was just released and has joined the JuliaStrings Github organization. Special thanks to @PatrickHaecker for major contributions to this release.

StaticStrings.jl contains several string types which wrap a NTuple{N, UInt8} for strings of fixed length strings similar to InlineStrings.jl. The main difference is that StaticStrings.jl uses an NTuple whereas InlineStrings.jl uses integer types as a backing store.

Here are the release notes for version 0.3:

Release Notes

Diff since v0.2.6

  • Breaking changes for CStaticString
  • Use of unsafe_string and unsafe_write for performance
  • Deprecate StaticStrings.data in favor of Tuple
  • Many contributions by @PatrickHaecker

Merged pull requests:

Closed issues:

  • StaticStrings on GPU, code_warntype on StaticString generation (#20)

Upstream issue for JuliaLang/Julia

In reviewing Patrick’s work, I realized that Julia is missing facilities to populate a Vector{UInt8} or Memory{UInt8} from a NTuple{N,UInt8} efficiently. I created an issue documenting the lack of autovectorization for this route and potential new methods for copyto!.

Future Plans

  • I was originally inspired to create this package to address fixed length strings coming from I/O libraries such as HDF5.jl. I never finished actually integrating this with I/O packages, so I hope to pursue that.
  • I also noticed that an AbstractString that wraps an existing Memory{UInt8} may be useful. I am unsure whether to add this to StaticStrings or if I should start a new package.
  • Your pull requests are welcome.

I think the new StringView type in 1.14 or in StringViews.jl can do this.

Interesting. An alternative implementation of a StaticString could be a StringView of a StaticVector from StaticArrays.jl.

Technically definitely interesting. However, from an ecosystem point-of-view it will probably take quite some time until StringView is available in Julia lts. While Julia 1.13 has the potential to be the next lts, an lts with StringView could still be 3 to 4 years away.

So in order to be useful now, this probably would need to be behind an interface with a fallback to a different implementation for older Julia versions with the same functionality only being slower.

I believe the core devs are aiming for 1.14 as the new LTS, but, like previous LTS releases, this will only be decided when 1.15 is released (as then can then retroactively judge if 1.14 was good enough to become LTS). So we’ll probably have StringView in the next LTS.

Indeed, @mkitti and @PatrickHaecker is it one of the design goals of StringView strings could be implemented in terms of them. If you want to use string views on Julia versions < 1.14, you should use StringViews.jl. This is an established package, and is the implementation that has been ported to Base. On Julia version 1.14 and newer, StringViews.jl is a stub package that simply re-exports the Base type and does nothing else. StringViews.jl is compatible with Julia 1.6.

I recommend you move the implementation to using string views, and then upstream PRs to any issues. Other than it being slightly annoying to have to upstream issues to Base, in theory there should be no downside, and adhering to one underlying implementation would help consolidate the semantics around strings-versus-codeunits in Julia.

I tried this package for the first time. FWIW, compile-time parsing works:

julia> using StaticStrings

julia> const s = static"123";

julia> function sum_digits()
           sum(convert(Int, c-UInt8('0')) for c in codeunits(s))
       end
sum_digits (generic function with 1 method)

julia> sum_digits()
6

julia> @code_typed sum_digits() # the compiled code is a trivial return
CodeInfo(
1 ─     return 6
) => Int64

Realize that the StringView type in Juia 1.14 is essentially just the code from StringViews.jl (which supports Julia 1.6+) ported into Base (with a little bit of code de-duplication). Performance-wise, there should be essentially no difference.

An implementation using StringViews and StaticArrays seems like it could be pretty minimal. If you do:

using StringViews, StaticArrays

const StaticString{N} = StringView{SVector{N, UInt8}} # type alias

macro static_str(s)
    bytes = tuple(codeunits(s)...)
    return :(StringView(SVector($bytes)))
end

then a lot of things just work:

julia> s = static"🚀"
"🚀"

julia> typeof(s)
StringView{SVector{4, UInt8}}

julia> s isa StaticString{4}
true

julia> s[1]
'🚀': Unicode U+1F680 (category So: Symbol, other), input as \:rocket:<tab>

julia> codeunits(s)
4-element SVector{4, UInt8} with indices SOneTo(4):
 0xf0
 0x9f
 0x9a
 0x80

However, if you want to define custom operations, like static concatenation of two static strings, then seems like it will be tricky right now to avoid type piracy unless you define a wrapper type and forward a lot of methods, or use a different function name (rather than overloading a Base function like *), or push the implementation into StaticArrays.jl.

I guess it depends on what kind of use-case you have in mind. If you just want to store them efficiently (e.g. in an array or an HDF5 file), then it’s not clear whether you need much more than the type above.

(If the implementation becomes small enough, it’s not crazy to me to simply make SString a type defined in StaticArrays.jl … then you can write whatever custom methods you want and it won’t be type piracy.)