Targets
In the previous chapter we've seen that we can only interact with Julia inside a scope, where we can use a frame to root managed data. If we look at the signature of any method we've called with a frame, we see that these methods are generic and can take an instance of any type that implements the Target
trait. Their return type also depends on this target type.
Take the signature of Call::call0
, for example:
unsafe fn call0<'target, Tgt>(self, target: Tgt) -> ValueResult<'target, 'data, Tgt>
where
Tgt: Target<'target>;
Any type that implements Target
is called a target. There are two things a target encodes: whether the result is rooted, and what lifetime restrictions apply to it.
If we call call0
with &mut frame
, ValueResult
is Result<Value, Value>
. &frame
also implement Target
, if we call call0
with it the result is left unrooted, and ValueResult
is Result<ValueRef, ValueRef>
. We say that &mut frame
is a rooting target, and &frame
is a non-rooting target.
The difference between Value
and ValueRef
is that Value
is guaranteed to be rooted, ValueRef
isn't. It's unsafe to use a ValueRef
in any meaningful way. Distinguishing between rooted and unrooted data at the type level helps avoid accidental use of unrooted data and running into use-after-free issues, which can be hard to debug. Every managed type has a Ref
alias, we'll call instances of these types unrooted references [to managed data].
The Result
alias is used with functions that catch exceptions, otherwise ValueData
is used instead; ValueResult
is defined as Result<ValueData, ValueData>
. Every managed type has a Result
and Data
alias.