This question already has answers here:
When did `guard let foo = foo` become legal?
(1 answer)
Why isn't guard let foo = foo valid?
(1 answer)
Closed 5 years ago.
I'm reading from Erica Sadun's Swift Style book and playing around with some code.
She suggests:
Prefer using same-name shadowing in conditional bind. “This approach is both conventional and safe:
guard let x = x { ... } // yes
guard let y = x { ... } // no
Deliberate same-name shadowing ensures the the unwrapped value’s role follows the logical intent established by the name prior to the if or guard statement. Introducing a new name blurs the line of those semantics and can prove dangerous. Consistent same-named shadowing prevents you from adding an unintentional shadow to symbols in the parent scope.
Regardless of this being a good opinion or not:
In my code, I'm trying to do:
guard let data = data else{...}
but I get the following error:
Definition conflicts with previous value
A simplified version of my function is:
func process(){
data : AnyObject?
if x == nil{ data = m}
else if x > 5{ data = j}
guard let data = data else { return } // Definition conflicts with previous value
somefunc(data)
}
If I place the guard statement inside the if or else if then the error would go away. But that's not what I want.
Is there any workaround to still be able to do same-name shadow.
EDIT:
I tried using backticks and I get the following errors:
Expected Pattern
Braced block of statements is an unused closure
Related
It seems logical to me that escaping closures would capture structs by copying. But if that was the case, the following code makes no sense and should not compile:
struct Wtf {
var x = 1
}
func foo(){
var wtf = Wtf()
DispatchQueue.global().async {
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
print("x = \(wtf.x)")
}
Yet it compiles successfully and even prints 5 when foo is called. How is this possible?
While it might make sense for a struct to be copied, as your code demonstrates, it is not. That's a powerful tool. For example:
func makeCounter() -> () -> Int {
var n = 0
return {
n += 1 // This `n` is the same `n` from the outer scope
return n
}
// At this point, the scope is gone, but the `n` lives on in the closure.
}
let counter1 = makeCounter()
let counter2 = makeCounter()
print("Counter1: ", counter1(), counter1()) // Counter1: 1 2
print("Counter2: ", counter2(), counter2()) // Counter2: 1 2
print("Counter1: ", counter1(), counter1()) // Counter1: 3 4
If n were copied into the closure, this couldn't work. The whole point is the closure captures and can modify state outside itself. This is what separates a closure (which "closes over" the scope where it was created) and an anonymous function (which does not).
(The history of the term "close over" is kind of obscure. It refers to the idea that the lambda expression's free variables have been "closed," but IMO "bound" would be a much more obvious term, and is how we describe this everywhere else. But the term "closure" has been used for decades, so here we are.)
Note that it is possible to get copy semantics. You just have to ask for it:
func foo(){
var wtf = Wtf()
DispatchQueue.global().async { [wtf] in // Make a local `let` copy
var wtf = wtf // To modify it, we need to make a `var` copy
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
// Prints 1 as you expected
print("x = \(wtf.x)")
}
In C++, lambdas have to be explicit about how to capture values, by binding or by copying. But in Swift, they chose to make binding the default.
As to why you're allowed to access wtf after it's been captured by the closure, that's just a lack of move semantics in Swift. There's no way in Swift today to express "this variable has been passed to something else and may no longer be accessed in this scope." That's a known limitation of the language, and a lot of work is going into fix it. See The Ownership Manifesto for more.
This question already has answers here:
Is there difference between using a constructor and using .init?
(2 answers)
Closed 3 years ago.
Why we need to use init method explicitly while we can create an object without it
class Details {
}
var obj = Details()
var obj = Details.init()
What's the difference between these two instance creations
Both are allowed and are equivalent. As The Swift Programming Language says:
If you specify a type by name, you can access the type’s initializer without using an initializer expression. In all other cases, you must use an initializer expression.
let s1 = SomeType.init(data: 3) // Valid
let s2 = SomeType(data: 1) // Also valid
let s3 = type(of: someValue).init(data: 7) // Valid
let s4 = type(of: someValue)(data: 5) // Error
So, when you’re supplying the name of the type when instantiating it, you can either use the SomeType(data: 3) syntax or SomeType.init(data: 3). Most Swift developers would favor SomeType(data: 3) as we generally favor brevity unless the more verbose syntax lends greater clarity, which it doesn’t in this case. That having been said, SomeType.init(data: 3) is permitted, though it is less common in practice.
Class() is just a shorthand for Class.init()
Both are interpreted by the compiler as exactly the same statements with no difference at all.
Summary:
I made a mistake in my Swift code and I've fixed it. Then I asked myself why this happened and how I could avoid it. I tried some ways but nothing helps.
I put the mistake and my thinking below. I hope you could teach me the right method to avoid this kind of mistake, but any idea, hint, or suggestion would be appreciated.
How can I avoid this kind of logic error?
The following is an excerpt from my assignment for Stanford cs193p course.
class SomeClass {
...
var accumulator: Double?
func someFunc() {
// I use `localAccumulator` and write `self.` for clarity.
if let localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
The problem here is that the line B has changed the value of instance value self.accumulator, and the line C should use the new value stored in self.accumulator, but it uses the outdated local var localAccumulator which was copied from self.accumulator's old value.
It was easy to find out the logic error via debugger. But then I reflected on my mistake, and was trying to look for a method to avoid this kind of logic error.
Method 1: use nil checking rather than optional binding
if self.accumulator != nil { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: self.accumulator!) // C
}
Actually, what really matters here is the force unwrapped self.accumulator!, it ensures the value comes from the real source. Using nil checking rather than optional binding can force me to force unwrap on self.accumulator.
But in some Swift style guides(GitHub, RayWenderlich, LinkedIn), force unwrapping is not encouraged. They prefer optional binding.
Method 2: use assertions.
if localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
assert(localAccumulator == self.accumulator) // D
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
I insert a assertion to check whether the localAccumulator is still equal to self.accumulator. This works, it will stop running once self.accumulator is modified unexpectedly. But it's so easy to forget to add this assertion line.
Method 3: SwiftLint
To find a way to detect this kind of error, I've skimmed SwiftLint's all rules, and also got a basic understanding of
SourceKitten(one of SwiftLint's dependencies). It seems too complicated to detect this kind of error by SwiftLint, especially when I make this pattern more general.
Some similar cases
Case 1: guard optional binding
func someFunc() {
guard let localAccumulator = self.accumulator { // A
return
}
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
In this case, it's much more difficult for human to notice the error, because localAccumulator has a broader scope with guard optional binding than if optional binding.
Case 2: value copy caused by function passing parameters
// Assume that this function will be called somewhere else with `self.accumulator` as its argument, like `someFunc(self.accumulator)`
func someFunc(_ localAccumulator) {
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
In this case, localAccumulator copies from self.accumulator when this function is called, then self.accumulator changes in the line B, the line C expects the self.accumulator's new value, but get its old value from localAccumulator.
In fact, the basic pattern is as below,
var x = oldValue
let y = x
functionChangingX() // assign x newValue
functionExpectingX(y) // expecting y is newValue, but it's oldValue
x ~ self.accumulator
y ~ localAccumulator
functionChangingX ~ performPendingBinaryOperation
functionExpectingX ~ PendingBinaryOperation.init
This error pattern looks like so common that I guess there should be a name for this error pattern.
Anyway, back to my question, how can I avoid this kind of logic error?
This example shows what I understood from your problem:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
addLastNameToGlobalName() // modifies global var "name"
//
// Here, you want to use the updated "name" value. As you mention, at this point,
// "name" could/might/should be different than "personName" (it could
// even be nil).
//
greet(person: ???) // use "name"? (which is optional), "personName"?, or what?
}
}
To me, the general approach is the problem here. The fact that you are:
Checking that a global optional-value is not nil
Calling a function that alters this global optional-value
Wanting to use the updated global optional-value as a non-optional value
A simple change in the approach/design of greeting this person would allow you to avoid the type of "logic error" that you mention.
Example 1:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
greet(person: fullName(for: personName))
}
}
Example 2:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
let fullName = addLastNameToGlobalName() // modifies global var "name" and returns the new value
greet(person: fullName)
}
}
It's possible to do the following in Swift:
let params: String
if let aString = someOptionalString {
params = "?someparam=\(aString)"
} else {
params = ""
}
However it would be much more concise if I could write it like:
let params = let aString = someOptionalString ? "?someparam=\(aString)" : ""
Or
let params = case let aString = someOptionalString ? "?someparam=\(aString)" : ""
However this doesn't compile in any way I could think about. Is this possible? If not, why not? And is there a way to suggest it to be implemented or can I only try to first add this myself to the Swift project and then propose the change to the community?
Because mapping an optional is a much more sensible choice:
let params = someOptionalString.map{ "?someparam\($0)" } ?? ""
To answer your question as given, conditional let only applies in an if statement or similar branching construct, everywhere else it doesn't have a value.
Normally when you have a pattern like:
if let x = y {
doSomething(x)
}
...what you're doing is declaring a new namespace context inheriting from the current one where x is defined; whether the code enters that context depends on whether the assigning expression evaluates to nil. Outside the block, x is not defined and it's an error to refer to it. If you like, think of it as a closure, something that might look like:
callIfNotNil(y, {x in doSomething(x)})
When you do a let otherwise, you are defining it in the current namespace, which means it can't not be defined on the other side of the ternary operator, so the best the compiler could give you is String! as a type, which would defer the nil check to runtime, largely defeating the point of using it.
In principle ternary could apply the same behaviour by defining an implicit block in the middle of the expression, but that's a recipe for confusion on the part of the programmer.
As for why let x = true; let y = x ? 1 : 2 is valid but let y = let x = true ? 1 : 2 isn't, there are some trivial precedence problems there, and with the let keyword being a compile-time feature not a runtime one it would be misleading to allow it mid-expression.
This question already has answers here:
println dictionary has "Optional"
(2 answers)
Closed 7 years ago.
If the following code runs
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
print(airports["YYZ"])
Why does the console print
Optional("Toronto Pearson")
Why does it print Optional( withValue ) and not just the value?
Why would I need to know that in the console?
Swift has optional types for operations that may fail. An array index like airports["XYZ"] is an example of this. It will fail if the index is not found. This is in lieu of a nil type or exception.
The simplest way to unwrap an optional type is to use an exclamation point, like so: airports["XYZ"]!. This will cause a panic if the value is nil.
Here's some further reading.
You can chain methods on option types in Swift, which will early exit to a nil without calling a method if the lefthand value is nil. It works when you insert a question mark between the value and method like this: airports["XYZ"]?.Method(). Because the value is nil, Method() is never called. This allows you to delay the decision about whether to deal with an optional type, and can clean up your code a bit.
To safely use an optional type without panicking, just provide an alternate path using an if statement.
if let x:String? = airports["XYZ"] {
println(x!)
} else {
println("airport not found")
}