Reputation: 15533
I have a problem that I don't know exactly how to model into Rust, in regard with ownership, lifetime and all that.
I have a large struct:
struct LargeStruct {
x: f32,
n: i32,
// lot of other fields.
}
The struct being large, I want to avoid copies, and therefore I carry around pointers to it.
I have a foo
function that takes a pointer to LargeStruct
and a f32
value. If this value is larger than the field x
, I want to create a new object with x
set to this value, and returns it. If it is not larger, then I want to return the original pointer itself.
I naively implemented it like this:
fn foo(object: &LargeStruct, y: f32) -> &LargeStruct {
if object.x < y {
LargeStruct {
x: y,
n: object.n,
// ...
}
}
else {
object
}
}
But it does not work: the two branches of the if do no return the same type. In the first case, I actually return a LargeStruct
, and in the second I return a &LargeStruct
. If I modify the object construction to take its pointer:
&LargeStruct {
then it doesn't work either: the lifetime of the object constructed is too short, so I can not return that from the function.
If I try to build the object on the heap:
~LargeStruct {
I have now another compilation error:
if and else have incompatible types: expected
~LargeStruct
but found&LargeStruct
(expected &-ptr but found ~-ptr)
I tried to specify a lifetime in the function signature:
fn foo<'a>(object: &'a LargeStruct, y: f32) -> &'a LargeStruct {
But it does not help: I don't know how to build a new LargeStruct
with the same lifetime.
I am calling this function like this:
fn main() {
let object = LargeStruct{
x: 1.0,
n: 2,
// ...
};
let result = foo(&object, 2.0);
}
Upvotes: 3
Views: 391
Reputation: 90842
It is not possible to design something in this way with it completely transparent; a reference must always have a lifetime not in excess of the scope of its owner; it is thus impossible to return a reference to an object whose scope does not exceed the function.
This can be solved in a way with an enum specifying the return type as either a reference to a LargeStruct or a LargeStruct, but it's clumsy:
pub struct LargeStruct {
a: int,
}
pub enum LargeStructOrRef<'a> {
LargeStructRef(&'a LargeStruct),
LargeStruct(LargeStruct),
}
fn foo<'a>(object: &'a LargeStruct, y: f32) -> LargeStructOrRef<'a> {
if object.x < y {
LargeStruct(LargeStruct {
x: y,
n: object.n,
// ...
})
} else {
LargeStructRef(object)
}
}
You'd then need to do pattern matching between LargeStruct
and LargeStructRef
—it can't be made transparent to the caller.
You can probably design this in a different way which will resolve these difficulties. For example, you might make foo
take a &mut LargeStruct
and modify the struct rather than creating a new one if object.x < y
. (This is most likely to be the design you actually want, I think.)
Upvotes: 3
Reputation: 16660
The intent behind your approach is to only return a modified copy under certain conditions if I understand it right. In Rust this can be modeled with a function that returns an Option<~LargeStruct>
type (perhaps even with Option<LargeStruct>
but I'm not sure if the compiler can efficiently move large objects in this case).
fn foo(object: &LargeStruct, y: f32) -> Option<~LargeStruct> {
if object.x < y {
return Some(~LargeStruct {
x: y,
//n: object.n,
// ...
})
}
None
}
As for why your approach didn't work: Rust doesn't let you return a reference to an object that will be freed once the function returns. A lifetime is a way to say that an object must live at least as long as the references to it.
Upvotes: 3