Dealing with String and Array slices

From what I can see, you cannot simply pass a array or string slice to a function that expects an array or string:

func sayHi(name: String) {
      print("Hello \(name)")
}

You can't simply do:

let sentence = "My name is Dan"
sayHi(sentence.prefix(3))

Why si that? And what is a solution to dealing with that? Just create a new String and Array out of the slices with String(stringSlice) and Array(array[1...4])?

"Why?"

Because Array and ArraySlice are completely different concrete types. As far as the type system is concerned, they’re no more similar than Int and String.

Re: copying slices into full collections

This is one approach, which is sometimes appropriate.

Slices act as cheap views onto the same underlying memory as the thing they’re slicing. This is advantageous because you can split really large collections up and slice them every which way, without ever wasting time on needless copies.

When you promote a slice to the “full” type like this, you trigger that copy to happen.

Working with slices without copying

The other approach is use protocols and/or generics to modify your function to accept a broader set of inputs.

For example, StringProtocol lets you generalize over "string-like" things like String and StringSlice.

// in Swift 5.7 and later, you can simplify this to;
// `func sayHi(name: some StringProtocol) {`
func sayHi<S: StringProtocol>(name: S) {
    print("Hello \(name)")
}

let sentence = "My name is Dan"
sayHi(name: sentence)
sayHi(name: sentence.prefix(3))

This lets your function operate on these slices, without commiting to copying their full contents.

Warning: don't leak memory using slices

You should be careful though. Only use slices for fast computations over your input, but don't persist them for long, as that will keep your (potentially large) input buffer alive for longer than necessary.

Instead, once you're done your slices, do one final copy into the "full" types, and use that from there on out. The large buffer will be deallocated once all slices referencing it expire.

3 Likes

In the case of Array and ArraySlice, you might opt to accept any kind of Collection. This has the added benefit of supporting other types like Set and Dictionary, etc.:

// in Swift 5.7 and later, you can simply this to:
// func printItems(_ items: some Collection<Int>)
func printItems<C: Collection>(_ items: C) where C.Element == Int {
    print("items in the \(type(of: items)):")
    for item in items {
        print(" * \(item)")
    }
}

let anArray = [10, 20, 30, 40, 50]
let aSet = Set(anArray)

printItems(anArray)
printItems(anArray.prefix(3))
printItems(aSet.prefix(3))
2 Likes