init(bytesNoCopy was deprecated in macOS 13

Now init(bytesNoCopy is deprecated in macOS 13
The message says : String does not support no-copy initialization.

The problem with this depreciation is not so much the "no-copy" but 2 arguments that are no longer available.

There is case where length is useful , when we have to deal with part of string pointer.

And now its a problem to get it because there is no string initialization with length.
Yes we can do it with substring or using NSString

NSString.init(bytesNoCopy ...) as String

but it is not a good solution.

The other argument missing is "freeWhenDone" who avoid to do a "defer dealloc" when the String is returned from a function.

Last point, there can be no other type than UInt8 or Int8, but there are cases where for example in ascii UInt16 is used. (Process 2 bytes at a time )

Here is some suggestions :

init?(bytes: UnsafeMutableRawPointer, length: Int, encoding: String.Encoding, freeWhenDone flag: Bool )

if the length is nil then the pointer is null terminated.

init(utf8Bytes: UnsafePointer<UInt8>, length: Int?, freeWhenDone flag: Bool)
init(utf8Bytes: UnsafePointer<Int8>, length: Int?, freeWhenDone flag: Bool)

Sometime it can be useful with bufferPointer

init(utf8BufferPointer: UnsafeBufferPointer<Int8>)
init(utf8BufferPointer: UnsafeBufferPointer<UInt8>)

This depreciation was made by people who didn't think about the consequences and no alternative is offered.

This operation can be spelled using String(decoding:as:), wrapping the bytes and length in an UnsafeRawBufferPointer.

This is String(cString:).

As noted above, these are String(decoding:as:).

4 Likes

I'm a bit confused as the deprecation message on bytesNoCopy states

String does not support no-copy initialization

Does that only apply to init, while String(decoding:as:) does an equivalent setup of a Swift String without a copy?

String does not support no-copy initialization.
String(decoding:as:) is an initializer.

Thanks. I just reread the original post and lukasa's answer, and realized the comments were not so much on the noCopy part. If String no longer supports a no-copy initialization, are there any alternatives (read only at the least - in the case where we already have some string data allocated elsewhere)?

String has never supported no-copy initialization, which is why the misleadingly-named initializer was deprecated. There’s no alternative way to make a String without copying. About the closest you could get is lazily bridging a no-copy NSString, but that will be much much slower for other reasons in almost all cases.

2 Likes

If you're doing something like, say, reading a text file, you may be able to use String(unsafeUninitializedCapacity:initializingUTF8With:). Rather than "string data allocated elsewhere", it is still an allocation owned by String, though.

As for the idea of taking ownership of an externally-allocated buffer without copying, we call those "shared strings". The implementation supports it, but it is not currently exposed by the API.

2 Likes

String does not support no-copy initialization

Its is discussed here

The problem is the length and freeWhenDone.

Now we have to use Buffer method such as (UnsafeBufferPointer start: count:) to conform to Collection protocol instead of UnsafeRawPointer and length.

Concerning freeWhenDone there is no more free pointer using String initialization.

Its the case for Data.init but no more for String.init.

"defer" sometimes do not work, i got random result in the caller.

Its must be done like this :

let str = String.int...ptr
ptr.deallocate
return str

instead of

return String.init...ptr...freeWhenDone true

When using Data does a copy take place? For example, in the following snippet, is unOwnedData copied in the String init. I am assuming it is at the moment.

var stringData: UnsafePointer<cchar>! = ...
let unownedData = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: stringData), count: stringDataLength, deallocator: .none)
let myString = String.init(data: unOwnedData, encoding: .utf8)

Yes, a copy takes place. A native Swift string has no way to be initialized without a copy.

Thanks. So, if I'd like to init a Swift String from an C array of cchar with a length (not null terminated), would I have to use one of init(bytes:encoding:) or init(data:encoding:)? Is there a preference to either?

For char (I'm assuming UTF8), use init(decoding:as:), specifying UTF8.self.

The initializers you mentioned should be used for legacy encodings (such as, say, SHIFT-JIS), or multi-byte Unicode encodings where the endianness differs from the host.

Bytes vs. Data depends on the type your bytes are stored in. Use whatever is most convenient for you; they should behave identically.

1 Like

The reason people keep suggesting using init(decoding:as:) rather than others is because init(bytes:…) and init(data:…) are from Foundation and may produce bridged Strings, where init(decoding:as:) is from Swift itself and always produces a native String.

4 Likes

The reason people keep suggesting …

Also, init(decoding:as:) produces a non-optional value, replacing any bogus UTF-8 with the replacement character (U+FFFD REPLACEMENT CHARACTER).

Of course, this may or may not be what you want (-:

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

Ah, good call mentioning that. We should probably expose validating rather than correcting versions of these things too.

2 Likes

For completeness there's this kinky call:

String.decodeCString(..., as:repairingInvalidCodeUnits:) -> (result: String, repairsMade: Bool)?

it returns whether any corrections were made, and it's doing corrections only with repairingInvalidCodeUnits = true.

We should probably expose validating rather than correcting versions
of these things too.

Yeah, I’d really like that when, for example, writing code that operates in a security-sensitive context. Indeed, I filed a bug about this a while ago (r. 99276048).

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

The problem is this depreciation has led to a reduction of the basic symmetry between String and Data concerning their initialisation, both being data containers.

Data.init(bytes: ..., count: ...)

has no equivalent for String

String(bytes: ..., count: ...,encoding:...)

does not exist

Data.init(bytesNoCopy: ..., count: ..., deallocator: ...)

no longer has an equivalent since

String.init(bytesNoCopy ...)

has been removed, deprecated.

This symmetry which existed in objective C has been removed.

We have to make a mumbo Jumbo with

UnsafeBuffer.init(start: ..., count: ...)

to be compatible with the Collection protocol.

Concerning memory release (freeWhenDone) which was very useful, it has no equivalent in any String initialisation.

This is independent of the way it is programmed underneath (C++), it is the swift language, its writing.

This depreciation of a function is in fact a regression in the Swift language.

1 Like

freeWhenDone didn't work properly in Swift either, because it would free it when the original hidden NSString was deallocated, not when the Swift String was deallocated (sometimes, but not always, the NSString would be wrapped in the String, which would have the desired effect at the cost of being very slow. But you couldn't count on it.).

This is not a regression. If there's a need for something like this, there should be a new API that actually works (and that will be challenging at best because String fundamentally does not support wrapping an external buffer).

1 Like

I'll also add that this symmetry doesn't exist. String is not equivalent to Data, despite being a data container, just as Dictionary is not equivalent to Set. These two types should not be expected to have equivalent API surface.

3 Likes