Description
🚀 The feature, motivation and pitch
ExecuTorchValue
is a lightweight Objective‑C wrapper for the C++ EValue
type. It provides a unified variant type that can hold a tensor, a scalar (boolean, integer, or double), a string, or lists of tensors/scalars. The wrapper uses standard Objective‑C types (NSNumber
, NSString
, NSArray
) to represent these values, and it is designed to be easily bridged into Swift via NS_SWIFT_NAME
annotations, so that users can create and manipulate values in an idiomatic way without having to deal with the underlying C++ complexity.
By using ExecuTorchValue
, developers no longer need to write custom bridging code for converting between C++ EValues
and Objective‑C types. This simplifies integration, improves type safety, and minimizes performance overhead. In Swift the value type appears simply as Value
with natural initializers, for example Value(3.14)
, Value("Hello")
, Value([1, 2, 3])
or Value(Tensor(...))
.
Alternatives
See the parent issue #8360 for motivation and alternatives overview.
Additional context
- The wrapper supports the most common types needed for model inputs and outputs: tensor, boolean, integer, double, string, and lists of tensors or scalars.
- Advanced features (such as support for optional tensor lists or separate lists for booleans, integers, or doubles) are supported via the fact
NSNumber
is already a variant type for different scalar values, andNSArray
as the list type may containNSNull
values denoting optional or missing values. - The design focuses on minimal overhead and seamless interoperation with ExecuTorch’s
Module
wrapper.
RFC (Optional)
Internal Representation
The ExecuTorchValue
wrapper holds a simple tag and an associated Objective‑C object. The tag (of type ExecuTorchValueTag
) indicates the type of value stored (e.g. tensor, string, double, integer, boolean, tensor list, scalar list). The internal value is stored as an id and is used to represent scalars via NSNumber
, strings via NSString
, and lists via NSArray
.
ExecuTorchValueTag _tag;
id _value;
Key Types:
The tag type directly matches the one in executorch/runtime/core/tag.h
:
typedef NS_ENUM(uint32_t, ExecuTorchValueTag) {
ExecuTorchValueTagNone,
ExecuTorchValueTagTensor,
ExecuTorchValueTagString,
ExecuTorchValueTagDouble,
ExecuTorchValueTagInteger,
ExecuTorchValueTagBoolean,
ExecuTorchValueTagBooleanList,
ExecuTorchValueTagDoubleList,
ExecuTorchValueTagIntegerList,
ExecuTorchValueTagTensorList,
ExecuTorchValueTagScalarList,
ExecuTorchValueTagOptionalTensorList,
} NS_SWIFT_NAME(ValueTag);
The actual data types are aliased as the following:
typedef NSNumber *ExecuTorchScalarValue NS_SWIFT_NAME(ScalarValue);
typedef BOOL ExecuTorchBooleanValue NS_SWIFT_NAME(BoolValue);
typedef NSInteger ExecuTorchIntegerValue NS_SWIFT_NAME(IntegerValue);
typedef double ExecuTorchDoubleValue NS_SWIFT_NAME(DoubleValue);
typedef NSString *ExecuTorchStringValue NS_SWIFT_NAME(StringValue);
typedef NSArray<ExecuTorchTensor *> *ExecuTorchTensorListValue NS_SWIFT_NAME(TensorListValue);
typedef NSArray<ExecuTorchScalarValue> *ExecuTorchScalarListValue NS_SWIFT_NAME(ScalarListValue);
Those may need an extra NS_SWIFT_BRIDGED_TYPEDEF
attribute to bridge well into Swift.
Key Methods:
- Initialization:
These class methods create a new ExecuTorchValue
instance with the appropriate tag and underlying value.
Objective-C
+ (instancetype)valueWithTensor:(ExecuTorchTensor *)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithBoolean:(ExecuTorchBooleanValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithInteger:(ExecuTorchIntegerValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithDouble:(ExecuTorchDoubleValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithString:(ExecuTorchStringValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithTensorList:(ExecuTorchTensorListValue)value NS_SWIFT_NAME(init(_:));
+ (instancetype)valueWithScalarList:(ExecuTorchScalarListValue)value NS_SWIFT_NAME(init(_:));
Swift:
let tensorValue = Value(someTensor)
let boolValue = Value(true)
let intValue = Value(42)
let doubleValue = Value(3.14)
let stringValue = Value("Hello")
let tensorList = Value([tensor1, tensor2])
let scalarList = Value([1, 2, 3])
- Properties:
The wrapper exposes properties to retrieve the stored value in its native type if it matches the expected type:
Objective-C
@property(nonatomic, readonly) ExecuTorchValueTag tag;
@property(nullable, nonatomic, readonly) ExecuTorchTensor *tensorValue NS_SWIFT_NAME(tensor);
@property(nullable, nonatomic, readonly) ExecuTorchScalarValue scalarValue NS_SWIFT_NAME(scalar);
@property(nonatomic, readonly) ExecuTorchBooleanValue boolValue NS_SWIFT_NAME(boolean);
@property(nonatomic, readonly) ExecuTorchIntegerValue intValue NS_SWIFT_NAME(integer);
@property(nonatomic, readonly) ExecuTorchDoubleValue doubleValue NS_SWIFT_NAME(double);
@property(nullable, nonatomic, readonly) ExecuTorchStringValue stringValue NS_SWIFT_NAME(string);
@property(nullable, nonatomic, readonly) ExecuTorchTensorListValue tensorListValue NS_SWIFT_NAME(tensorList);
@property(nullable, nonatomic, readonly) ExecuTorchScalarListValue scalarListValue NS_SWIFT_NAME(scalarList);
They will return nil
if the Value
is of a different type, and for the numeric types that cannot be nil
we can leverage NSParameterAssert
instead to check for correctness.
Swift
let value = Value(42)
print(value.tag) // e.g., prints .integer
print(value.integer) // prints 42
print(value.scalar) // prints 42 as NSNumber
print(value.tensor) // prints nil, since value does not contain a tensor
let textValue = Value("Hello")
print(textValue.tag) // prints .string
print(textValue.string) // prints "Hello"
- Type Checking
There should be a bunch of readonly properties to check if the Value is of a certain type:
@property(nonatomic, readonly) BOOL isNone;
@property(nonatomic, readonly) BOOL isTensor;
@property(nonatomic, readonly) BOOL isScalar;
@property(nonatomic, readonly) BOOL isBoolean;
@property(nonatomic, readonly) BOOL isInteger;
@property(nonatomic, readonly) BOOL isDouble;
@property(nonatomic, readonly) BOOL isString;
@property(nonatomic, readonly) BOOL isTensorList;
@property(nonatomic, readonly) BOOL isScalarList;
That will be used as following:
Objective-C
Value *value = [Value valueWithInteger:42];
if (value.isInteger) {
NSLog(@"Integer: %@", value.intValue);
} else {
NSLog(@"Not an integer");
}
Value *tensorValue = [Value valueWithTensor:someTensor];
if (tensorValue.isTensor) {
NSLog(@"Tensor: %@", tensorValue.tensorValue);
} else {
NSLog(@"Not a tensor");
}
Swift
let value1 = Value(42)
if value1.isInteger {
print("Integer: \(value1.integer)")
} else {
print("Not an integer")
}
let value2 = Value(someTensor)
if value2.isTensor {
print("Tensor: \(value2.tensor)")
} else {
print("Not a tensor")
}
let value3 = Value("Hello")
if value3.isString {
print("String: \(value3.string)")
} else {
print("Not a string")
}
- Equality:
It also provides convenience methods to check the type of the stored value and an equality method:
Objective-C
- (BOOL)isEqualToValue:(nullable ExecuTorchValue *)other;
Swift:
let isTensor = value.tag == .tensor
let isEqual = value == anotherValue
A sample implementation may look like the following:
- (BOOL)isEqual:(nullable id)other {
if (self == other) {
return YES;
}
if (![other isKindOfClass:[ExecuTorchValue class]]) {
return NO;
}
return [self isEqualToValue:(ExecuTorchValue *)other];
}
- (BOOL)isEqualToValue:(nullable ExecuTorchValue *)other {
if (!other) {
return NO;
}
return _tag == other->_tag && [_value isEqual:other->_value];
}
- Description
It comes handy to implement some -description
method for better logging and debugging:
- (NSString *)description {
return [NSString stringWithFormat:@"Value {\ntag=%i,\nvalue=%@}", _tag, _value];
}
- Error Handling & Reporting:
The wrapper’s accessor properties are intentionally nullable. When an accessor is called for a type that does not match the stored value (for example, calling tensor on a scalar value), it returns nil per Objective‑C conventions. This design safely reports missing values without triggering runtime errors and provides a natural experience in Swift.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status