Borrowing
A borrowed value is a reference or a pointer to an object that something else owns. A script can use it during one call. A script cannot store it.
Parts of this guide
This guide has 2 pages. This page covers what borrowing is, where it is allowed and rejected, and handles.
- Borrowed locals, calls and fixes: borrowed locals, borrowed values in calls, the escape hatch and quick fixes.
What borrowing is
A value (Vec3) is a copy. A handle (a script class, or a native class with a stub) is a smart pointer. A borrowed value is a ref or in parameter, a T*, a ref local or &x. The state block of a script object moves when the script reloads. A stored pointer into the block would dangle. A handle is safe: its pointee does not move.
using "Actor.h";
class Hero : Actor {
Vec3 home; // value: a copy in the state block
$Hero buddy = null; // handle: safe to store
void Move(ref Vec3 to) { } // borrowed: valid during the call
}
Where borrowing is allowed
| Position | Example | Allowed |
|---|---|---|
| Parameter | void F(ref Vec3 v), void F(Vec3* v) |
yes |
| Local | ref r = x;, Vec3* p = &x;, var p = &x; |
yes |
catch, lambda parameter, foreach |
catch (std.exception e), (ref Vec3 v) => ..., foreach (ref x in xs) |
yes |
&x in a call, new or base(...) |
f(&x), new $Hero(&x) |
yes |
| Field of a native class with a weak source | Weapon* last; |
yes: stored as a checked pointer (see “Pointer fields”) |
| Field of another type, return type, collection element, lambda capture | ref Vec3 last;, () => p.x |
no |
&x in an assignment or return |
p = &x; |
no |
using "Actor.h";
class Hero {
Hero(int* n) { }
void Show(ref Vec3 p) { }
void Run(ref Vec3 pos, int total) { // parameter
try { Show(pos); }
catch (std.exception e) { Show(pos); } // catch
var f = (ref Vec3 v) => { Show(v); }; // lambda parameter
std.vector<int> xs = std.vector<int>();
foreach (ref x in xs) x = x + 1; // foreach
ref r = total; // local reference
var p = &pos; // local pointer
var h = new $Hero(&total); // &x as new argument
}
}
Where borrowing is rejected
A field, a return value, a collection element, a lambda capture and a stored &x are errors. A field of the move-only type std.unique_ptr<T> is also an error, because a reload copies each field. A pointer field of a native class with a weak source is the exception (see “Pointer fields”).
// error
using "Actor.h";
class Bad {
Vec3 last;
Vec3* ptr; // pointer to a value type
std.vector<Vec3*> all; // collection element
std.unique_ptr<Vec3> own; // move-only field
Vec3* Addr() { return &last; } // return value, return &x
void Run(ref Vec3 v) {
last = &v; // assignment of &x
var f = () => { v.x = 1; }; // capture
}
}
a field cannot hold a pointer to the value type 'Vec3': nothing owns it, so the pointer can dangle. Use a value, or a pointer to the object that owns it
a collection element cannot hold a borrowed pointer or reference to 'Vec3'. Declare a stub ...
field of move-only type 'std::unique_ptr<Vec3>' is not supported: a reload copies each field. Use a handle
a return value cannot hold a borrowed pointer or reference to 'Vec3'. Declare a stub ...
a '&' value cannot be stored in an assignment, a 'return' or a field initializer. Use '&' only for a call argument or a local
the lambda captures 'v', a borrowed pointer or reference. A closure cannot keep it. Copy the value or use a handle
Handles and borrowing
. on a handle means a member of the pointee. &h on a handle is an error. Pass h for a handle parameter, or for a native T* or T& parameter: the translator converts the handle (see “Native objects as parameters” in native-interop.md). &*h and *h stay valid. A ref parameter of a script class is a reference to the handle, so the callee can rebind it. this is not a variable: copy it to a local first. An intrusive stub is an exception: its T* is the handle, so a field can have it.
using "Actor.h"; using "Weapon.h"; using "Armory.h"; using "Object.h";
[native, handle("std::shared_ptr<$T>")] class Weapon;
[native, handle("$T*"), intrusive("AddRef", "Release")] class Object;
class Hero : Actor {
$Hero buddy = null;
Object* rc = null; // intrusive: the pointer is the handle
void Pair(ref $Hero dst) { dst = buddy; } // dst is the handle itself
void Join(ref $Hero other) { }
void Run(ref Armory a, Weapon h) {
a.Use(h); a.Hit(h); // native T* parameter, native T& parameter: the plain handle
a.Use(&*h); a.Hit(*h); // the same, written out
$Hero me = this; Join(me); // this is not a variable: pass a copy
}
}
An override can take a native Weapon* or Weapon& as the plain handle Weapon. The body then owns a handle, not a borrowed pointer. See “Parameter forms of an override” in inheritance.md.
A Weapon* field is not a finding when Weapon has a weak source. The field is a checked pointer (see “Pointer fields”). A Weapon* field of a class with no weak source is a finding. Hero* of a script class is always an error. The this error shows after the other errors are fixed.
A weak handle [weak] T x does not keep its object alive, so it breaks a cycle of handles. Each read locks it for one statement. So ref r = x.hp and &x.hp are errors: the reference dangles. See “Weak handles” in types-handles.md.
// error
using "Actor.h"; using "Mesh.h"; using "Armory.h";
[native] class Mesh;
[native, handle("std::shared_ptr<$T>")] class Weapon;
class Hero : Actor {
Mesh* last;
Hero* buddy;
void Join(ref $Hero other) { }
void Run(ref Armory a, Weapon h) {
a.Use(&h);
Join(this);
}
}
a field cannot hold a borrowed pointer or reference to 'Mesh'. Declare a stub with 'handle' or 'intrusive' for 'Mesh', or use a value
a pointer to the script class 'Hero' is not allowed: use '$Hero' (a handle)
'&' on the handle 'h' is not allowed. Pass 'h' for a handle parameter or a native 'T*' or 'T&' parameter, or use '&*h' for a 'T*' and '*h' for a 'T&'
'this' cannot bind to the reference parameter 'other' of the script class 'Hero'. Copy it to a local first: '$Hero me = this;'
Pointer fields
A field or a module variable of type T* is allowed when T is a native class with a weak source. The translator stores it as hot::CheckedPtr<T>. The pointer is weak: it does not keep the object alive. It reads null after the object dies. It keeps its target across a reload.
A weak source is one of these:
- The class derives from
std::enable_shared_from_this. - A stub gives the class a non-intrusive
handleorweak_handle. - The host specializes
hot::weak_ref_traits<T>(see “Custom handles” intypes-handles.md).
using "Actor.h"; using "Equip.h";
class Hero : Actor {
Charm* target = null; // a checked pointer
override void Tick(float dt) {
target?.Bump(1); // a dead target reads as null: the call does nothing
}
}
The use is the same as for a pointer. p == null, p = null, p.M(), and Use(p) for a native T* parameter work. A call on a null or dead pointer throws NullReference. A native call that returns a raw T* can assign to the field when the class has std::enable_shared_from_this. An object that no std::shared_ptr owns throws NullReference at the assignment. A class with a non-intrusive stub and no enable_shared_from_this accepts a handle only: p = h;.
The object can die while a native call uses it. A native T& from *p is borrowed, like a raw pointer. Do not keep it.
A field with an intrusive stub stays the handle: Object* is the handle (see above). These forms are errors:
| Form | Error |
|---|---|
| A reference field | "&" is not a type suffix; write ref T x. A field cannot be a reference. Use T* or a value. |
int* p;, byte* b;, a pointer to an enum |
a field cannot hold a pointer to the value type 'int': nothing owns it, so the pointer can dangle. Use a value, or a pointer to the object that owns it |
[weak] Weapon* w; |
a pointer field is already a weak reference: remove '[weak]' |
Mesh* m; where Mesh has no weak source |
a field cannot hold a borrowed pointer or reference to 'Mesh'. Declare a stub with 'handle' or 'intrusive' for 'Mesh', or use a value |
Weapon**, void*, a return type or a collection element Weapon* |
the borrowed message above |
A pointer to a script class is still an error. Use the handle Hero.