I have a very small repo which currently does not compile, with the following error:
error[E0597]: `line` does not live long enough
--> src/main.rs:19:20
|
19 | let line = line.trim();
| ^^^^-------
| |
| borrowed value does not live long enough
| argument requires that `line` is borrowed for `'static`
...
22 | }
| - `line` dropped here while still borrowed
I've had trouble producing a minimal example demoing the problem: this (playground) works as expected, despite that fn main is identical to the one which fails, as are the signatures of ItemParser::parse and Item::pretty_to.
Inlining a section of fn main, and some signatures:
let parser = ItemParser::new();
let stdin = stdin();
let reader = stdin.lock();
let reader = BufReader::new(reader);
let stdout = stdout();
let mut writer = stdout.lock();
for line in reader.lines() {
let line = line?;
let line = line.trim();
let item = parser.parse(line)?;
item.pretty_to(&mut writer)?;
}
The same issue persists when I comment out item.pretty_to(&mut writer)?;, so I believe that that isn't the problem.
I can't show the actual code for ItemParser as it's generated by LALRPOP, but the signature as reported by rustdoc is
pub struct ItemParser { /* fields omitted */ }
pub fn parse<'input>(
&self,
input: &'input str
) -> Result<Item<'input>, ParseError<usize, Token<'input>, &'static str>>
Unlike this issue, nothing in this crate explicitly requires a 'static lifetime.
My expectation is that at the head of the for loop, item has type io::Result<String>. After we discard the error and trim the edges, it should have type &'a str, where 'a is the lifetime scoped to this iteration of the for loop. In that case, parsing the line should produce an Item<'a> with the same lifetime. It drops before the lifetime ends, in appropriate sequence. As nothing visibly requests a 'static lifetime, I don't know where that requirement is coming from.
On error, parser.parse() yields a type that is bounded to the lifetime of the input.
Result<Item<'input>, ParseError<usize, Token<'input>, &'static str>>
// ^^^^^^^^^^^^^
You're using ? to return the error from main (not "discard" it), which by necessity will outlive the loop, and therefore line.
You can handle the error immediately via match or if let, or do something like parser.parse().map_err(...)? to transform the error into something not bounded to the lifetime of line.
Answering the title specifically, The 'static requirement is from using eyre::Result which is an alias for Result<T, E = Report>. Report can be created from any Error type (which ParseError is), but is constrained to be 'static. It is a shame the compiler doesn't bring this up though.
Since str is a sort of slice, it can not outlive its parent. The result on trim is really just a slice the original string.
pub fn trim(&self) -> &str
// Now with the implicit lifetimes
pub fn trim<'a, 'b: 'a>(&'b self) -> &'a str
Since none of the lifetimes of the function arguments would not make appropriate bounds for the lifetime of the function output, it must have a different lifetime and the only appropriate lifetime for an unconstrained reference is 'static.
Its basically the equivalent to returning a reference to data you just freed in c.
Foo *data_to_return = &foo->bar;
free(foo);
return data_to_return;
But it's kinda a moot point anyway because the function would never work in the first place. The lifetime is more of a symptom of your issue than the cause. Just ignore the lifetime for now since it sounds like its holding you up from actually fixing the issue. Once you fix your code, the 'static bound will go away.
Related
The following two rust functions are identical other than the fact that one returns an immutable reference and the other returns a mutable one. Since neither involves borrowing something multiple times, I don't see why the two sould work any differently. However, the one with the mutable reference results in a compile error, while the one with the immutable reference does not:
// This complies with no problems
fn foo<'a>() {
let _: &'a () = &();
}
// This does not compile (see error below)
fn foo_mut<'a>() {
let _: &'a mut () = &mut ();
}
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:14:30
|
13 | fn foo_mut<'a>() {
| -- lifetime `'a` defined here
14 | let _: &'a mut () = &mut ();
| ---------- ^^ creates a temporary which is freed while still in use
| |
| type annotation requires that borrow lasts for `'a`
15 | }
| - temporary value is freed at the end of this statement
For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground` due to previous error
It is also possibly relevant that when there are no explicit lifetimes, the code also has no problem compiling:
// This also compiles with no problem
fn foo_mut_without_lifetime() {
let _: &mut () = &mut ();
}
It seems the only thing that is causing a problem is trying to store a mutable reference with a lifetime, and that immutable references and references without explicit lifetimes have no issue. Why is this happening, and how can I get around it?
Note that there's nothing special about () or generic lifetimes here. This compiles fine:
fn allowed() -> &'static i32 {
let x = &3;
let y: &'static i32 = x;
y
}
And this does not:
fn not_allowed() -> &'static mut i32 {
let x = &mut 3;
let y: &'static mut i32 = x;
y
}
So why is the immutable reference allowed?
When you take a reference of a value, Rust infers the lifetime based on where the value's going to die. Here's an example:
let y;
{
let x = 3;
y = &x;
println!("{y}"); // works fine, `y` is still alive
} // `x` will get dropped at the end of this block
println!("{y}"); // fails to compile, the lifetime of `y` has expired (since `x` has died)
Since x dies at the end of the block, Rust knows that the lifetime of the y reference should only extend until the end of the block as well. Hence, it stops you from using it after x is dead.
This seems pretty obvious. But take a moment to think. In the following code:
let x;
{ // block A
x = &3;
}
What is the inferred lifetime of x? You may be tempted to say "the same as block A". But this would in fact be incorrect. Why? Because Rust is a bit smarter than that. It knows that 3 is a constant, and therefore Rust can fit 3 into the constant table of the final executable. And since the constant table will last as long as the lifetime of the final program, Rust can infer that the expression &3 has a 'static lifetime. Then everything works out fine, since &'static can be cast to any other lifetime as required!
Rust draws an explicit line between constants and temporaries, and one of the benefits of having a constant expression is that taking an immutable reference of any constant will always yield a 'static lifetime. This is not true of temporaries. The following code will not compile:
fn f() -> &'static String {
let x = &String::new();
let y: &'static String = x;
y
}
This is because for temporaries, Rust can't put them in the constant table of the executable, since they have to be computed on-demand, and therefore share the same lifetime as the scope they're in.
Okay, this is great, but why isn't the mutable reference of a constant allowed to be 'static?
There are two problems with allowing this:
On some architectures, constant tables can't be modified. This is true of WASM and some embedded architectures, as well as all Harvard-architecture machines. Providing a &mut reference would just be complete nonsense, since they're not mutable. And such fundamental borrow checker rules should really not differ between platforms.
A &'static mut reference is dangerous, since it is quite literally a global variable.
I have this method
async fn insert<'a>(&mut self, params: &'a[&'a dyn QueryParameters<'a>]) {
let mut mapped_fields: String = String::new();
let values: &'a[&'a QueryParameters<'a>] = &[#(#insert_values),*];
// #insert_transaction
}
I would like to know why the compiler and the borrow checker complains about this:
^^^^^^^^^-
| | |
| | temporary value is freed at the end of this statement
| creates a temporary which is freed while still in use
| lifetime `'a` defined here
| type annotation requires that borrow lasts for `'a`
If I just remove the 'a lifetime annotation from the explicit type declaration, everything works. This piece of code is inside a derive proc-macro that it's marked as async-trait, so values is getting 'life0 (if I do not explicitly declare it's lifetime bound as 'a), which is the same as the &mut self.
But, I am confused. I make a new variable (values), as a slice array, but... that variable doesn't goes anywhere. I mean. I am just creating the slice on the let values assignment, but doing anything with it. So, Drop should come at the end of the method and cleaning up the variables for the current stack frame. Fine. But, I am doing nothing with that slice.
Can someone help me to understand better the borrow checker?
You can get a similar error with a simpler code:
fn test<'a>(x: &'a str) {
let p: &'a str = &String::new();
}
That results in:
error[E0716]: temporary value dropped while borrowed
--> src/lib.rs:2:23
|
1 | fn test<'a>(x: &'a str) {
| -- lifetime `'a` defined here
2 | let p: &'a str = &String::new();
| ------- ^^^^^^^^^^^^^ creates a temporary which is freed while still in use
| |
| type annotation requires that borrow lasts for `'a`
3 | }
| - temporary value is freed at the end of this statement
The cause is that the 'a lifetime is a generic argument to the function, that is decided by the caller of that function, and that lifetime will certainly contain the whole execution of the function. For example if calling it with:
let z = String::from("foo");
test(&z);
Then 'a will be the lifetime of that z argument.
Now, when you use that lifetime to annotate a local value such as:
let p: &'a str = &String::new();
or equivalently:
let temp = String::new();
let p: &'a str = &temp;
you are requiring that this reference lives at least as long as 'a. But that is certainly impossible, because temp is a local value, it will never outlive the function itself.
This is precisely what the error is trying to say: you define a lifetime 'a as a generic argument, then you borrow a value that is required to live for 'a because of a type annotation, but that value is dropped sooner than it should be.
Naturally, if you use the argument of the function such as in:
let p: &'a str = &*x;
then all goes well, because *x is explicitly declared to live for 'a.
On the other hand, if you omit the lifetime annotation:
let p: &str = &temp;
then the compiler will just guess the lifetime of temp as a local lifetime and assign that to this reference. And all will go well, too.
I'm trying to have a MyType that supports a From<&[u8]> trait, but I'm running into "lifetime problems":
Here's a minimally viable example:
struct MyType {
i: i32
}
impl MyType {
fn from_bytes(_buf: &[u8]) -> MyType {
// for example...
MyType { i: 3 }
}
}
impl From<&[u8]> for MyType {
fn from(bytes: &[u8]) -> Self {
MyType::from_bytes(bytes)
}
}
fn do_smth<T>() -> T where T: From<&[u8]>
{
// for example...
let buf : Vec<u8> = vec![1u8, 2u8];
T::from(buf.as_slice())
}
(...and here's a Rust playground link)
For reasons I cannot understand, the Rust compiler is telling me:
error[E0637]: `&` without an explicit lifetime name cannot be used here
--> src/lib.rs:17:36
|
17 | fn do_smth<T>() -> T where T: From<&[u8]>
| ^ explicit lifetime name needed here
I'm not an expert on lifetimes and I don't understand why this piece of code needs one. What would be the best way to fix this?
Might Rust be thinking that the type T could be a &[u8] itself? But, in that case, the lifetime should be inferred to be the same as the input to From::<&[u8]>::from(), no?
One fix I was given was to do:
fn do_smth<T>() -> T where for<'a> T: From<&'a [u8]>
{
// for example...
let buf : Vec<u8> = vec![1u8, 2u8];
T::from(buf.as_slice())
}
...but I do not understand this fix, nor do I understand why lifetimes are needed in the first place.
Rust first wants you to write:
fn do_smth<'a, T>() -> T
where
T: From<&'a [u8]>,
{
// for example...
let buf: Vec<u8> = vec![1u8, 2u8];
T::from(&buf)
}
where you make explicit that this function can be called for any lifetime 'a and any type T such that T implements From<&'a [u8]>.
But Rust then complains:
error[E0597]: `buf` does not live long enough
--> src/lib.rs:24:13
|
18 | fn do_smth<'a, T>() -> T
| -- lifetime `'a` defined here
...
24 | T::from(&buf)
| --------^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `buf` is borrowed for `'a`
25 | }
| - `buf` dropped here while still borrowed
You promised that this function could work with any lifetime, but this turns out to not be true, because in the body of the function you create a fresh reference to the local Vec which has a different lifetime, say 'local. Your function only works when 'a equals 'local, but you promise that it also works for all other lifetimes. What you need is a way to express that these lifetimes are the same, and the only way I think that is possible is by changing the local reference to an argument:
fn do_smth<'a, T>(buf: &'a [u8]) -> T
where
T: From<&'a [u8]>,
{
T::from(buf)
}
And then it compiles.
If instead of the function promising it can work with any lifetime, you want to make the caller promise it can work with any lifetime, you can instead use HRTBs to make the caller promise it.
fn do_smth<T>() -> T
where
for<'a> T: From<&'a [u8]>,
{
// for example...
let buf: Vec<u8> = vec![1u8, 2u8];
T::from(&buf)
}
Now, since you can use any lifetime, a local one also works and the code compiles.
Lifetimes represent a "duration" (metaphorically), or, more pragmatically, a scope, in which a variable is valid. Outside of one's lifetime, the variable should be considered as having been freed from memory, even though you haven't done that explicitly, because that's how Rust manages memory.
It becomes a bit more complex when Rust tries to ensure that, once a variable is done for, no other parts of the code that could have had access to that variable still have access. These shared accesses are called borrows, and that's why borrows have lifetimes too. The main condition Rust enforces on them is that a borrow's lifetime is always shorter (or within, depending on how you see it) than its original variable, ie. you can't share something for more time than you actually own it.
Rust therefore enforces all borrows (as well as all variables, really) to have an established lifetime at compile-time. To lighten things, Rust has default rules about what a lifetime should be if it was not explicitly defined by the user, that is, when you talk about a type that involves a lifetime, Rust let's you not write that lifetime explicitly under certain conditions. However, this is not a "lifetime inference", in the sense of inferring types: Rust will not try to make sense out of explicit lifetimes, it's a lot less smart about it. In particular, this lifetime explicitation can fail, in the sense that Rust will not be able to figure out the right lifetime it has to assign even though it was possible to find out that worked.
Back to business: your first error simply stems from the fact that Rust has no rule to make a lifetime if it wasn't provided in the position pointed out by the error. As I said, Rust won't try to infer what the right lifetime would be, it simply checks whether not explicitly putting a lifetime there implicitly means something or not. So, you simply need to put one.
Your first reflex might be to make your function generic over the missing lifetime, which is often the right thing to do (and even the only possible action), that is, do something like that:
fn do_smth<'a, T>() -> T
where
T: From<&'a [u8]>
{
// for example...
let buf : Vec<u8> = vec![1, 2];
T::from(buf.as_slice())
}
What this means is that do_smth is generic over the lifetime 'a, just like it is generic over the type T. This has two consequences:
Rust will proceed to a monomorphisation of your function for each call, meaning it will actually provide a concrete implementation of your function for each type T and each lifetime 'a that is required. In particular, it will automatically find out what is the right lifetime. This might seem contradictory with what I said earlier, about Rust not inferring lifetimes. The difference is that type inference and monomorphisation, although similar, are not the same step, and so the compiler does not work lifetimes in the same way. Don't worry about this until you have understood the rest.
The second consequence, which is a bit disastrous, is that your function exposes the following contract: for any type T, and any lifetime 'a, such that T: From<&'a [u8]>, do_smth can produce a type T. If you think about it, it means that even if T only implements From<&'a [u8]> for a lifetime 'a that is already finished (or, if you see lifetimes as scopes, for a lifetime 'a that is disjoint from do_smth's scope), you can produce an element of type T. This is not what you actually meant: you don't want the caller to give you an arbitrary lifetime. Instead, you know that the lifetime of the borrow of the slice is the one you chose it to be, within your function (because you own the underlying vector), and you want that the type T to be buildable from that slice. That is, you want T: From<&'a [u8]> for a 'a that you have chosen, not one provided by the caller.
This last point should make you understand why the previous snippet of code is unsound, and won't compile. Your function should not take a lifetime as argument, just a type T with certain constraints. But then, how do you encode the said conditions? That's where for<'a> comes into play. If you have a type T such that T: for<'a> From<&'a [u8]>, it means that for all 'a, T: From<&'a [u8]>. In particular, it is true for the lifetime of your slice. This is why the following works
fn do_smth<T>() -> T
where
T: for<'a> From<&'a [u8]>
{
// for example...
let buf: Vec<u8> = vec![1, 2];
T::from(buf.as_slice())
}
Note that, as planned, this version of do_smth is not generic over a lifetime, that is, the caller does not provide a lifetime to the function.
Consider the following Rust program:
#![feature(generic_associated_types)]
pub trait Func {
type Input<'a>;
type Output;
fn call(self, input: Self::Input<'_>) -> Self::Output;
}
fn invoke<'cx, F>(f: F, ctx: &'cx mut u8)
where F: 'static + Func<Input<'cx> = &'cx u8, Output = u8>,
{
let input = &*ctx;
let out = f.call(input);
*ctx = out;
}
I've used #![feature(generic_associated_types)], but I think the question I'm asking is still relevant if you move 'a from Func::Input to Func and use a higher-rank trait bound on invoke.
This code errors, but I don't think it's unsound:
error[E0506]: cannot assign to `*ctx` because it is borrowed
--> src/lib.rs:15:5
|
10 | fn invoke<'cx, F>(f: F, ctx: &'cx mut u8)
| --- lifetime `'cx` defined here
...
13 | let input = &*ctx;
| ----- borrow of `*ctx` occurs here
14 | let out = f.call(input);
| ------------- argument requires that `*ctx` is borrowed for `'cx`
15 | *ctx = out;
| ^^^^^^^^^^ assignment to borrowed `*ctx` occurs here
First ctx is reborrowed as input, which is passed to f.call and then never used again. f.call returns a value that does not contain any lifetimes (u8: 'static), so there is no connection between out and ctx.
Likewise, the type of f contains no lifetimes (F: 'static), so it cannot hold a reference with lifetime 'cx. Furthermore, the lifetime 'cx cannot be safely coerced to 'static inside call, so there's no way to "smuggle out" a reference with that lifetime that's accessible beyond the invocation of f.call. Therefore, I don't see how anything can alias ctx, and I think assigning to it in the last line should be sound.
Am I missing something? Would accepting this code be unsound? If not, why does Rust fail to take advantage of 'static bounds in this way?
The lifetime 'cx could be 'static meaning input can be smuggled elsewhere and be invalidated by *ctx = out.
There's no way to constrain that a lifetime is strictly less than another, so I don't think adding a "broader" lifetime constraint to a generic type is even considered by the borrow checker.
The code as written is unsound. The lifetime bound of 'static on F is completely irrelevant, because the lifetime bounds of F::Input and F are two distinct lifetimes, and it's the associated type's lifetime that's causing the error. By declaring F::Input<'ctx> = &'ctx u8, you are declaring that the immutable borrow lives the length of the mutable one, making the mutable reference unsafe to use.
As #Stargateur mentioned, the thing that can make this work are Higher Ranked Trait bounds:
fn invoke<F>(f: F, ctx: &mut u8)
where F: for<'ctx> Func<Input<'ctx> = &'ctx u8, Output = u8>,
{
let input = ctx;
let out = f.call(input);
*input = out;
}
Playground
That is, instead of declaring that the function call is valid for some specific lifetime 'ctx it is valid for all lifetime's 'ctx. This way, the compiler can freely pick an appropriate lifetime for the reborrow to make this work.
As a side note, you might think that using two specific lifetimes in the function definition would be able to work, but any attempt to do so results in the compiler failing to choose the appropriate lifetime that makes things work.
I was reading the lifetimes chapter of the Rust book, and I came across this example for a named/explicit lifetime:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
It's quite clear to me that the error being prevented by the compiler is the use-after-free of the reference assigned to x: after the inner scope is done, f and therefore &f.x become invalid, and should not have been assigned to x.
My issue is that the problem could have easily been analyzed away without using the explicit 'a lifetime, for instance by inferring an illegal assignment of a reference to a wider scope (x = &f.x;).
In which cases are explicit lifetimes actually needed to prevent use-after-free (or some other class?) errors?
The other answers all have salient points (fjh's concrete example where an explicit lifetime is needed), but are missing one key thing: why are explicit lifetimes needed when the compiler will tell you you've got them wrong?
This is actually the same question as "why are explicit types needed when the compiler can infer them". A hypothetical example:
fn foo() -> _ {
""
}
Of course, the compiler can see that I'm returning a &'static str, so why does the programmer have to type it?
The main reason is that while the compiler can see what your code does, it doesn't know what your intent was.
Functions are a natural boundary to firewall the effects of changing code. If we were to allow lifetimes to be completely inspected from the code, then an innocent-looking change might affect the lifetimes, which could then cause errors in a function far away. This isn't a hypothetical example. As I understand it, Haskell has this problem when you rely on type inference for top-level functions. Rust nipped that particular problem in the bud.
There is also an efficiency benefit to the compiler — only function signatures need to be parsed in order to verify types and lifetimes. More importantly, it has an efficiency benefit for the programmer. If we didn't have explicit lifetimes, what does this function do:
fn foo(a: &u8, b: &u8) -> &u8
It's impossible to tell without inspecting the source, which would go against a huge number of coding best practices.
by inferring an illegal assignment of a reference to a wider scope
Scopes are lifetimes, essentially. A bit more clearly, a lifetime 'a is a generic lifetime parameter that can be specialized with a specific scope at compile time, based on the call site.
are explicit lifetimes actually needed to prevent [...] errors?
Not at all. Lifetimes are needed to prevent errors, but explicit lifetimes are needed to protect what little sanity programmers have.
Let's have a look at the following example.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
x
}
fn main() {
let x = 12;
let z: &u32 = {
let y = 42;
foo(&x, &y)
};
}
Here, the explicit lifetimes are important. This compiles because the result of foo has the same lifetime as its first argument ('a), so it may outlive its second argument. This is expressed by the lifetime names in the signature of foo. If you switched the arguments in the call to foo the compiler would complain that y does not live long enough:
error[E0597]: `y` does not live long enough
--> src/main.rs:10:5
|
9 | foo(&y, &x)
| - borrow occurs here
10 | };
| ^ `y` dropped here while still borrowed
11 | }
| - borrowed value needs to live until here
The lifetime annotation in the following structure:
struct Foo<'a> {
x: &'a i32,
}
specifies that a Foo instance shouldn't outlive the reference it contains (x field).
The example you came across in the Rust book doesn't illustrate this because f and y variables go out of scope at the same time.
A better example would be this:
fn main() {
let f : Foo;
{
let n = 5; // variable that is invalid outside this block
let y = &n;
f = Foo { x: y };
};
println!("{}", f.x);
}
Now, f really outlives the variable pointed to by f.x.
Note that there are no explicit lifetimes in that piece of code, except the structure definition. The compiler is perfectly able to infer lifetimes in main().
In type definitions, however, explicit lifetimes are unavoidable. For example, there is an ambiguity here:
struct RefPair(&u32, &u32);
Should these be different lifetimes or should they be the same? It does matter from the usage perspective, struct RefPair<'a, 'b>(&'a u32, &'b u32) is very different from struct RefPair<'a>(&'a u32, &'a u32).
Now, for simple cases, like the one you provided, the compiler could theoretically elide lifetimes like it does in other places, but such cases are very limited and do not worth extra complexity in the compiler, and this gain in clarity would be at the very least questionable.
If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.
Likewise, if a structure holds two references (as two member fields) then a member function of the structure may sometimes return the first reference and sometimes the second one. Again explicit lifetimes prevent such ambiguities.
In a few simple situations, there is lifetime elision where the compiler can infer lifetimes.
I've found another great explanation here: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references.
In general, it is only possible to return references if they are
derived from a parameter to the procedure. In that case, the pointer
result will always have the same lifetime as one of the parameters;
named lifetimes indicate which parameter that is.
The case from the book is very simple by design. The topic of lifetimes is deemed complex.
The compiler cannot easily infer the lifetime in a function with multiple arguments.
Also, my own optional crate has an OptionBool type with an as_slice method whose signature actually is:
fn as_slice(&self) -> &'static [bool] { ... }
There is absolutely no way the compiler could have figured that one out.
As a newcomer to Rust, my understanding is that explicit lifetimes serve two purposes.
Putting an explicit lifetime annotation on a function restricts the type of code that may appear inside that function. Explicit lifetimes allow the compiler to ensure that your program is doing what you intended.
If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called. It suffices to have a look at the annotations of functions that are directly called by that piece of code. This makes your program much easier to reason about for you (the compiler), and makes compile times managable.
On point 1., Consider the following program written in Python:
import pandas as pd
import numpy as np
def second_row(ar):
return ar[0]
def work(second):
df = pd.DataFrame(data=second)
df.loc[0, 0] = 1
def main():
# .. load data ..
ar = np.array([[0, 0], [0, 0]])
# .. do some work on second row ..
second = second_row(ar)
work(second)
# .. much later ..
print(repr(ar))
if __name__=="__main__":
main()
which will print
array([[1, 0],
[0, 0]])
This type of behaviour always surprises me. What is happening is that df is sharing memory with ar, so when some of the content of df changes in work, that change infects ar as well. However, in some cases this may be exactly what you want, for memory efficiency reasons (no copy). The real problem in this code is that the function second_row is returning the first row instead of the second; good luck debugging that.
Consider instead a similar program written in Rust:
#[derive(Debug)]
struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);
impl<'a, 'b> Array<'a, 'b> {
fn second_row(&mut self) -> &mut &'b mut [i32] {
&mut self.0
}
}
fn work(second: &mut [i32]) {
second[0] = 1;
}
fn main() {
// .. load data ..
let ar1 = &mut [0, 0][..];
let ar2 = &mut [0, 0][..];
let mut ar = Array(ar1, ar2);
// .. do some work on second row ..
{
let second = ar.second_row();
work(second);
}
// .. much later ..
println!("{:?}", ar);
}
Compiling this, you get
error[E0308]: mismatched types
--> src/main.rs:6:13
|
6 | &mut self.0
| ^^^^^^^^^^^ lifetime mismatch
|
= note: expected type `&mut &'b mut [i32]`
found type `&mut &'a mut [i32]`
note: the lifetime 'b as defined on the impl at 4:5...
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
In fact you get two errors, there is also one with the roles of 'a and 'b interchanged. Looking at the annotation of second_row, we find that the output should be &mut &'b mut [i32], i.e., the output is supposed to be a reference to a reference with lifetime 'b (the lifetime of the second row of Array). However, because we are returning the first row (which has lifetime 'a), the compiler complains about lifetime mismatch. At the right place. At the right time. Debugging is a breeze.
The reason why your example does not work is simply because Rust only has local lifetime and type inference. What you are suggesting demands global inference. Whenever you have a reference whose lifetime cannot be elided, it must be annotated.
I think of a lifetime annotation as a contract about a given ref been valid in the receiving scope only while it remains valid in the source scope. Declaring more references in the same lifetime kind of merges the scopes, meaning that all the source refs have to satisfy this contract.
Such annotation allow the compiler to check for the fulfillment of the contract.