pub trait ExtensionOp:
Debug
+ Send
+ Sync
+ 'static {
Show 13 methods
// Required methods
fn family_id(&self) -> &'static str;
fn payload_hash(&self, hasher: &mut dyn Hasher);
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool;
fn clone_arc(&self) -> Arc<dyn ExtensionOp> ⓘ;
fn as_any(&self) -> &dyn Any;
fn input_count(&self) -> usize;
fn output_count(&self) -> usize;
fn infer_output_meta(
&self,
ctx: &mut ExtensionShapeContext<'_>,
) -> Result<Vec<(DType, Vec<SymDim>)>>;
// Provided methods
fn semantic_effects(&self) -> ExtensionEffectDeclaration<'_> { ... }
fn semantic_aliases(&self) -> ExtensionAliasDeclaration<'_> { ... }
fn scalar_identity(&self) -> Option<&'static str> { ... }
fn lower_to_standard_ops(
&self,
_builder: &mut GraphBuilder<StdTensorOp>,
_inputs: &[ValueRef<StdTensorOp>],
_input_dtypes: &[DType],
_input_shapes: &[&[SymDim]],
) -> ExtensionLoweringResult { ... }
fn prune_outputs(
&self,
_live_outputs: &[bool],
) -> Option<Arc<dyn ExtensionOp>> { ... }
}Expand description
The contract every out-of-tree extension primitive must satisfy.
Implementations appear in the core graph as
StdTensorOp::Extension(Arc<dyn ExtensionOp>). Every method is part of the
ExtensionOp spec (docs/spec/extension-op.md); the short form:
- identity via
family_id+payload_hash - fixed arity via
input_count/output_count; - shape / dtype inference via
infer_output_meta; - optional fixed-shape standard-op expansion via
lower_to_standard_opsfor peer lowerers such as XLA that cannot execute extension runtimes; - AD via separately registered role-specific extension rules.
§Downcast convention
Implementations MUST also implement Any so that
ExtensionOp::payload_eq can downcast a trait-object reference to
the concrete type. The helper ExtensionOp::as_any returns
&dyn Any for this purpose. Implementations usually define it as
fn as_any(&self) -> &dyn Any { self }.
§Examples
use std::sync::Arc;
use tenferro_ops::ext_op::ExtensionOp;
use tenferro_ops::{ExtensionShapeContext, SymDim};
use tenferro_tensor::DType;
#[derive(Clone, Debug)]
struct IdentityExt;
impl ExtensionOp for IdentityExt {
fn family_id(&self) -> &'static str { "example.identity.v1" }
fn payload_hash(&self, _hasher: &mut dyn std::hash::Hasher) {}
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
other.as_any().downcast_ref::<IdentityExt>().is_some()
}
fn clone_arc(&self) -> Arc<dyn ExtensionOp> { Arc::new(self.clone()) }
fn as_any(&self) -> &dyn Any { self }
fn input_count(&self) -> usize { 1 }
fn output_count(&self) -> usize { 1 }
fn infer_output_meta(
&self,
ctx: &mut ExtensionShapeContext<'_>,
) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
Ok(vec![(ctx.input_dtype(0)?, ctx.input_shape(0)?.to_vec())])
}
}
let op: Arc<dyn ExtensionOp> = Arc::new(IdentityExt);
assert_eq!(op.input_count(), 1);Required Methods§
Sourcefn family_id(&self) -> &'static str
fn family_id(&self) -> &'static str
Stable, process-independent family identifier.
MUST be unique per extension family (payload schema), not per
instance, and MUST follow the reserved format
"<crate-name>.<op-name>.v<major>".
Sourcefn payload_hash(&self, hasher: &mut dyn Hasher)
fn payload_hash(&self, hasher: &mut dyn Hasher)
Hash the payload (everything except family_id).
Implementations MUST be pure and deterministic across calls on the same value. Hashes MUST NOT include transient state such as allocation addresses or atomically updated counters.
Sourcefn payload_eq(&self, other: &dyn ExtensionOp) -> bool
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool
Structural equality against another extension value.
The carrier’s PartialEq impl first compares family_ids. When the
family IDs match, it calls payload_eq. Implementations MUST return
true iff the payloads are semantically equal AND
other.family_id() == self.family_id().
Sourcefn clone_arc(&self) -> Arc<dyn ExtensionOp> ⓘ
fn clone_arc(&self) -> Arc<dyn ExtensionOp> ⓘ
Deep-clone the payload behind an Arc.
The carrier’s Clone impl uses Arc::clone on the fast path; this
method exists for rare cases that need a second independent Arc.
Sourcefn as_any(&self) -> &dyn Any
fn as_any(&self) -> &dyn Any
Upcast this extension to &dyn Any for downcasting in payload_eq.
Implementations SHOULD return self verbatim. The method is
object-safe (no Self: Sized bound) so it can be called on an
&dyn ExtensionOp; that’s what makes
other.as_any().downcast_ref::<ConcreteType>() work from
Self::payload_eq implementations.
Sourcefn input_count(&self) -> usize
fn input_count(&self) -> usize
Number of primal inputs. MUST be constant for any given
Arc<dyn ExtensionOp> value.
Sourcefn output_count(&self) -> usize
fn output_count(&self) -> usize
Number of outputs. MUST match the length of the vector returned by a
successful Self::infer_output_meta call.
Sourcefn infer_output_meta(
&self,
ctx: &mut ExtensionShapeContext<'_>,
) -> Result<Vec<(DType, Vec<SymDim>)>>
fn infer_output_meta( &self, ctx: &mut ExtensionShapeContext<'_>, ) -> Result<Vec<(DType, Vec<SymDim>)>>
Infer output dtypes and shapes for each output slot.
The canonical inference driver validates arity before invoking this
callback. Implementations MUST validate rank, dtype, axis, and other
input-derived metadata through ctx before using it. Invalid public
input must return a typed error rather than an empty sentinel or panic.
On success, the returned vector MUST have length self.output_count(),
one (dtype, shape) entry per output slot. Shapes use SymDim so
extension ops compose with graph-global symbolic metadata.
§Errors
Returns tenferro_tensor::Error::Validation for invalid rank, axis,
or dtype metadata, or tenferro_tensor::Error::RuntimeState when the
output contract cannot be inferred from unavailable metadata.
Provided Methods§
Sourcefn semantic_effects(&self) -> ExtensionEffectDeclaration<'_>
fn semantic_effects(&self) -> ExtensionEffectDeclaration<'_>
Declare observable semantic effects for this extension payload.
The compatibility default is deliberately Undeclared, not pure.
Semantic-program construction rejects an undeclared payload so an
extension cannot silently acquire purity during migration.
Sourcefn semantic_aliases(&self) -> ExtensionAliasDeclaration<'_>
fn semantic_aliases(&self) -> ExtensionAliasDeclaration<'_>
Declare semantic output aliasing for this extension payload.
The compatibility default is deliberately Undeclared, not fresh.
Execution-only users may continue to carry an older payload, while
semantic-program construction requires an explicit declaration.
Sourcefn scalar_identity(&self) -> Option<&'static str>
fn scalar_identity(&self) -> Option<&'static str>
Canonical identity of an externally defined scalar this operation carries.
A semantic program’s identity must be reproducible across processes, while an
externally defined scalar’s tag is a process-local TypeId. An operation
whose inputs or outputs are externally defined declares the stable name here,
and the runtime stamps it onto that operation’s external value metadata. The
name belongs to the contribution that owns the scalar, and two scalars must
not share one.
§Examples
use std::any::Any;
use std::hash::Hasher;
use tenferro_ops::ext_op::ExtensionOp;
use tenferro_ops::{ExtensionShapeContext, SymDim};
use tenferro_tensor::DType;
#[derive(Clone, Debug)]
struct Declared;
impl ExtensionOp for Declared {
fn family_id(&self) -> &'static str {
"example.declared.v1"
}
fn payload_hash(&self, _hasher: &mut dyn Hasher) {}
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn clone_arc(&self) -> std::sync::Arc<dyn ExtensionOp> {
std::sync::Arc::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn input_count(&self) -> usize {
1
}
fn output_count(&self) -> usize {
1
}
fn scalar_identity(&self) -> Option<&'static str> {
Some("example.scalar.v1")
}
fn infer_output_meta(
&self,
_context: &mut ExtensionShapeContext<'_>,
) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
Ok(vec![(DType::F64, Vec::new())])
}
}
#[derive(Clone, Debug)]
struct Undeclared;
impl ExtensionOp for Undeclared {
fn family_id(&self) -> &'static str {
"example.undeclared.v1"
}
fn payload_hash(&self, _hasher: &mut dyn Hasher) {}
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn clone_arc(&self) -> std::sync::Arc<dyn ExtensionOp> {
std::sync::Arc::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn input_count(&self) -> usize {
1
}
fn output_count(&self) -> usize {
1
}
fn infer_output_meta(
&self,
_context: &mut ExtensionShapeContext<'_>,
) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
Ok(vec![(DType::F64, Vec::new())])
}
}
// A contribution declares the identity of its own scalar, and an operation
// that carries no external scalar keeps the default.
assert_eq!(Declared.scalar_identity(), Some("example.scalar.v1"));
assert_eq!(Undeclared.scalar_identity(), None);Sourcefn lower_to_standard_ops(
&self,
_builder: &mut GraphBuilder<StdTensorOp>,
_inputs: &[ValueRef<StdTensorOp>],
_input_dtypes: &[DType],
_input_shapes: &[&[SymDim]],
) -> ExtensionLoweringResult
fn lower_to_standard_ops( &self, _builder: &mut GraphBuilder<StdTensorOp>, _inputs: &[ValueRef<StdTensorOp>], _input_dtypes: &[DType], _input_shapes: &[&[SymDim]], ) -> ExtensionLoweringResult
Try to expand this extension into standard tensor graph operations.
Return ExtensionStandardLowering::Lowered after adding only standard
StdTensorOp operations to builder.
ExtensionStandardLowering::Unsupported means a peer lowerer may try a
configured fallback; an ExtensionLoweringError remains a real
lowering failure and must not be converted into a capability miss.
§Errors
Returns ExtensionLoweringError when the payload or input metadata
cannot be lowered safely.
Sourcefn prune_outputs(&self, _live_outputs: &[bool]) -> Option<Arc<dyn ExtensionOp>>
fn prune_outputs(&self, _live_outputs: &[bool]) -> Option<Arc<dyn ExtensionOp>>
Optionally return an equivalent op that produces only live outputs.
live_outputs is aligned with this op’s current output slots. Return
None when the family does not support output pruning. Return
Some(op) only when the new op’s outputs are exactly the live output
slots, in ascending slot order, and op.output_count() equals the
number of true entries in live_outputs.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".