Why Can't I Mutate a Variable Initially Set to a Certain Parameter When the Func Was Called

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



Leave a reply



Submit