“ subscript”不可用: 不能使用 CountableClosed Range < Int > 下标 String,请参阅文档注释以进行讨论

在 Swift 4中,当我尝试使用下标语法获取 StringSubstring时,我得到了这个错误。

“ subscript”不可用: 不能使用 CountableClosed Range 下标 String,请参阅文档注释以供讨论

例如:

let myString: String = "foobar"
let mySubstring: Substring = myString[1..<3]

两个问题:

  1. 如何解决此错误?
  2. 错误中提到的“讨论的文档注释”在哪里?
54112 次浏览
  1. How can I resolve this error?

This error means you can't use an Int in the subscript format – you have to use a String.Index, which you can initialize with an encodedOffset Int.

let myString: String = "foobar"
let lowerBound = String.Index.init(encodedOffset: 1)
let upperBound = String.Index.init(encodedOffset: 3)
let mySubstring: Substring = myString[lowerBound..<upperBound]
  1. Where is "the documentation comment for discussion" that was referred to in the error?

It's on GitHub in the Swift Standard Library repository in a file called UnavailableStringAPIs.swift.gyb in the bottom of a locked filing cabinet stuck in a disused lavatory with a sign on the door saying 'Beware of the Leopard'. link

Your question (and self-answer) has 2 problems:

Subscripting a string with Int has never been available in Swift's Standard Library. This code has been invalid for as long as Swift exists:

let mySubstring: Substring = myString[1..<3]

The new String.Index(encodedOffset: ) returns an index in UTF-16 (16-bit) encoding. Swift's string uses Extended Grapheme Cluster, which can take between 8 and 64 bits to store a character. Emojis make for very good demonstration:

let myString = "🇺🇸🇨🇦🇬🇧🇫🇷"
let lowerBound = String.Index(encodedOffset: 1)
let upperBound = String.Index(encodedOffset: 3)
let mySubstring = myString[lowerBound..<upperBound]


// Expected: Canadian and UK flags
// Actual  : gibberish
print(mySubstring)

In fact, getting the String.Index has not changed at all in Swift 4, for better or worse:

let myString = "🇺🇸🇨🇦🇬🇧🇫🇷"
let lowerBound = myString.index(myString.startIndex, offsetBy: 1)
let upperBound = myString.index(myString.startIndex, offsetBy: 3)
let mySubstring = myString[lowerBound..<upperBound]


print(mySubstring)
  1. If you want to use subscripts on Strings like "palindrome"[1..<3] and "palindrome"[1...3], use these extensions.

Swift 4

extension String {
subscript (bounds: CountableClosedRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start...end])
}


subscript (bounds: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start..<end])
}
}

Swift 3

For Swift 3 replace with return self[start...end] and return self[start..<end].

  1. Apple didn't build this into the Swift language because the definition of a 'character' depends on how the String is encoded. A character can be 8 to 64 bits, and the default is usually UTF-16. You can specify other String encodings in String.Index.

This is the documentation that Xcode error refers to.

More on String encodings like UTF-8 and UTF-16

Based on p-sun's answer

Swift 4

extension StringProtocol {
subscript(bounds: CountableClosedRange<Int>) -> SubSequence {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(start, offsetBy: bounds.count)
return self[start..<end]
}


subscript(bounds: CountableRange<Int>) -> SubSequence {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(start, offsetBy: bounds.count)
return self[start..<end]
}
}

Notable changes:

  • Now an extension of StringProtocol. This allows adopters such as Substring to also gain these subscripts.
  • End indices are offset from the start index of the bounds rather than the start of the string. This prevents traversing from the start of the String twice. The index method is O(n) where n is the offset from i.

Building on both p-sun's and Justin Oroz's answers, here are two extensions that protect against invalid indexes beyond the start and end of a string (these extensions also avoid rescanning the string from the beginning just to find the index at the end of the range):

extension String {


subscript(bounds: CountableClosedRange<Int>) -> String {
let lowerBound = max(0, bounds.lowerBound)
guard lowerBound < self.count else { return "" }


let upperBound = min(bounds.upperBound, self.count-1)
guard upperBound >= 0 else { return "" }


let i = index(startIndex, offsetBy: lowerBound)
let j = index(i, offsetBy: upperBound-lowerBound)


return String(self[i...j])
}


subscript(bounds: CountableRange<Int>) -> String {
let lowerBound = max(0, bounds.lowerBound)
guard lowerBound < self.count else { return "" }


let upperBound = min(bounds.upperBound, self.count)
guard upperBound >= 0 else { return "" }


let i = index(startIndex, offsetBy: lowerBound)
let j = index(i, offsetBy: upperBound-lowerBound)


return String(self[i..<j])
}
}
extension String {


subscript(bounds: CountableClosedRange<Int>) -> String {
let lowerBound = max(0, bounds.lowerBound)
guard lowerBound < self.count else { return "" }


let upperBound = min(bounds.upperBound, self.count-1)
guard upperBound >= 0 else { return "" }


let i = index(startIndex, offsetBy: lowerBound)
let j = index(i, offsetBy: upperBound-lowerBound)


return String(self[i...j])
}


subscript(bounds: CountableRange<Int>) -> String {
let lowerBound = max(0, bounds.lowerBound)
guard lowerBound < self.count else { return "" }


***let upperBound = min(bounds.upperBound, self.count-1)***
guard upperBound >= 0 else { return "" }


let i = index(startIndex, offsetBy: lowerBound)
let j = index(i, offsetBy: upperBound-lowerBound)


return String(self[i..<j])
}
}

You getting this error because result of subscript with range is Substring? not Substring.

You must use following code:

let myString: String = "foobar"
let mySubstring: Substring? = myString[1..<3]

You could just convert your string to an array of characters...

let aryChar = Array(myString)

Then you get all the array functionality...