Why can't I mutate a variable initially set to a certain parameter when the func was called?
As Gil notes, this needs to be a class because you are treating it as a reference type. When you modify currentTimer
, you don't expect that to create a completely new Timers instance, which is what happens with a value type (struct). You expect it to modify the existing Timers instance. That's a reference type (class). But to make this work, there's quite a bit more you need. You need to tie the Timers to the View, or the View won't update.
IMO, the best way to approach this is let Timers track the current timeLeft
and have the view observe it. I've also added an isRunning
published value so that the view can reconfigure itself based on that.
struct TimerView: View {
// Observe timers so that when it publishes changes, the view is re-rendered
@ObservedObject var timers = Timers(intervals: [10, 6, 8, 14])
var body: some View {
Button(action: { self.timers.startTimer()} ) {
Text("Hello, World! \(timers.timeLeft)")
.foregroundColor(.white)
.background(timers.isRunning ? Color.red : Color.blue) // Style based on isRunning
.font(.largeTitle)
}
.disabled(timers.isRunning) // Auto-disable while running
}
}
// Timers is observable
class Timers: ObservableObject {
// And it publishes timeLeft and isRunning; when these change, update the observer
@Published var timeLeft: Int = 0
@Published var isRunning: Bool = false
// This is `let` to get rid of any confusion around what to do if it were changed.
let intervals: [Int]
// And a bit of bookkeeping so we can invalidate the timer when needed
private var timer: Timer?
init(intervals: [Int]) {
// Initialize timeLeft so that it shows the upcoming time before starting
self.timeLeft = intervals.first ?? 0
self.intervals = intervals
}
func startTimer() {
// Invalidate the old timer and stop running, in case we return early
timer?.invalidate()
isRunning = false
// Turn intervals into a slice to make popFirst() easy
// This value is local to this function, and is captured by the timer callback
var timerLengths = intervals[...]
guard let firstInterval = timerLengths.popFirst() else { return }
// This might feel redundant with init, but remember we may have been restarted
timeLeft = firstInterval
isRunning = true
// Keep track of the timer to invalidate it elsewhere.
// Make self weak so that the Timers can be discarded and it'll clean itself up the next
// time it fires.
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
// Decrement the timer, or pull the nextInterval from the slice, or stop
if self.timeLeft > 0 {
self.timeLeft -= 1
} else if let nextInterval = timerLengths.popFirst() {
self.timeLeft = nextInterval
} else {
timer.invalidate()
self.isRunning = false
}
}
}
}
How can a function argument allow mutating a variable but not be capable of pointing to any other variable?
A reference is a variable that points to something else. References do not collapse, so in Rust you can have a reference to a reference.
Your linked question describes what's different between x: &mut T
and mut x: T
. mut x: &T
means you can change where the reference points to, but you can't change the value what it points to.
Non-reference values will be copied when passed to functions (or moved, if not implementing Copy
).
A working example:
// with &mut I can change the referred value of n
fn mutate_usize_again(n: &mut usize) {
*n += 1;
*n += 70;
}
fn mutate_usize_two_times(n: &mut usize) {
*n = 8;
// just pass it along
mutate_usize_again(n);
}
fn mutate_usize_one_time_referred_value(n: &mut usize) {
*n += 25;
}
// this changes the **local** value of n
fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
println!("n before assigning in mutate_usize_one_time = {}", n);
n = 48;
println!("n after assigning in mutate_usize_one_time = {}", n);
}
fn main() {
let mut index = 0;
mutate_usize_one_time_mutable_pointer(index);
println!("index after mutate_usize_one_time_mutable_pointer = {}", index);
mutate_usize_two_times(&mut index);
println!("index after mutate_usize_two_times = {}", index);
mutate_usize_one_time_referred_value(&mut index);
println!("index after mutate_usize_one_time_referred_value = {}", index);
}
Correct Style for Python functions that mutate the argument
The first way:
def change(array):
array.append(4)
change(array)
is the most idiomatic way to do it. Generally, in python, we expect a function to either mutate the arguments, or return something1. The reason for this is because if a function doesn't return anything, then it makes it abundantly clear that the function must have had some side-effect in order to justify it's existence (e.g. mutating the inputs).
On the flip side, if you do things the second way:
def change(array):
array.append(4)
return array
array = change(array)
you're vulnerable to have hard to track down bugs where a mutable object changes all of a sudden when you didn't expect it to -- "But I thought change
made a copy"...
1Technically every function returns something, that _something_
just happens to be None
...
Mutate function parameters in Swift
It seems from your example that you need to change the passed arguments inside the function as a side effect, and need the updated values to be available after the function call. If that's the case, you need to use the inout
modifier.
Otherwise, if all you need is to modify the parameters inside of the function call, you can explicitly define them as variables on the function definition:
Using in-out parameters
First, change your function declaration to:
func exampleFunction(inout value: String, index: inout Int) -> Bool
Now, an in-out parameter has a value that is passed in to the function, is modified by the function, and then is passed back out of the function, replacing the original value. For this to work, you can't pass a literal into your function, since there would be nowhere to store the modified value afterwards. In fact, it has to be a variable.
You cannot pass a constant or a literal value as the argument, because constants can't be modified. Hence, change your function call to:
var passed = "passedValue"
var index = 2
var b = exampleFunction(&passed, &index)
After the call, both passed
and index
will contain the new values, as modified by the function.
Also, note the &
before each argument when calling the function. It must be there, to indicate the argument can be modified by the function.
Using variable parameters – Removed in Swift 3
In this case, all you need to do is change your function declaration to use variable parameters, as below:
func exampleFunction(var value: String, var index: Int) -> Bool
Changes made to the arguments inside the scope of the function, aren't visible outside the function or stored anywhere after the function call.
JavaScript, changing a variables value inside of a function when the variable is passed as a parameter
var x = 5
var z = function(y) { y = 10; }
console.log(x) // Outputs 5 instead of 10
Where exactly did you changed X? you dont have X and you did not call the function as well.
I assume you wish to do this:
var x = {x: 5}
var z = function(y) { y.x = 10; }
z(x);
console.log(x.x) // Outputs 10
The code is using x as variable on an object instead of a primitive number which is passed by value and not by reference and thus cannot be modified.
Using object you can modify the attributes (properties) of the object
When pass a variable to a function, why the function only gets a duplicate of the variable?
The are basically two schools of thought on this matter.
The first is pass-by-value where a copy of the value is created for the called function.
The second is pass-by-reference where the parameter that appears in the called function is an "alias" of the original. That means changes you make to it are reflected in the original.
C is generally a pass-by-value language. You can emulate pass-by-reference by passing the address of a variable and then using that to modify the original:
void setTo42 (int *x) { *x = 42; }
:
int y;
setTo42 (&y);
// y is now 42
but that's more passing the pointer to a variable by value, than passing the variable itself by reference.
C++ has true reference types, possibly because so many people have trouble with C pointers :-) They're done as follows:
void setTo42 (int &x) { x = 42; }
:
int y;
setTo42 (y);
// y is now 42
Pass-by-value is usually preferable since it limits the effects that a function can have on the "outside world" - encapsulation, modularity and localisation of effect is usually a good thing.
Being able to arbitrarily modify any parameters passed in would be nearly as bad as global variables in terms on modularity and code management.
However, sometimes you need pass-by-reference since it might make sense to change one of the variables passed in.
Why can't a variable which previously referred to None be modified?
If you're asking why data
's id
value is the same initially, then that's because the default argument is initialised once when the function is defined, and remains throughout the program. That's also the reason for the "Least Astonishment" and the Mutable Default Argument.
When data
is reassigned, all that happens is that it points to a new object (the empty list), and this object may or may not have the same id
(REPL sessions frequently reuse the same ids
of garbage collected variables).
Related Topics
Why Do Integers Not Conform to the Anyobject Protocol
Add Constraints to Generic Parameters in Extension
Print the Nstableview's Row Number of the Row Clicked by the User
Open a Filedialog in Swiftui on MACos
How to Increase (Animate) the Width of the Square on Both Ends
Xcode 11 Beta 3, Build Error "Unknown Attribute 'State'", "Use of Undeclared Type 'View'" etc
Use of Undeclared Type 'Attributedstring'
Swift 2.0 'Inout' Function Parameters and Computed Properties
What Makes a Property a Computed Property in Swift
How to Embed Third Party Framework on Ionic Capacitor Custom Plugin
How to Speed Up Updating Relationship Among Tables, After One or Both Tables Are Already Saved
How to Demonstrate a Zombie Object in Swift
How to Read a Property List from Data in Swift 3
Suppressing Implicit Returns in Swift