pub trait StructObject: Send + Sync {
    // Required method
    fn get_field(&self, name: &str) -> Option<Value>;

    // Provided methods
    fn static_fields(&self) -> Option<&'static [&'static str]> { ... }
    fn fields(&self) -> Vec<Arc<str>> { ... }
    fn field_count(&self) -> usize { ... }
}
Expand description

Provides the behavior of an Object holding a struct.

An basic object with the shape and behavior of a struct (that means a map with string keys) can be represented by this trait.

Simplified Example

For structs which do not need any special method behavior or methods, the Value type is capable of automatically constructing a wrapper Object by using Value::from_struct_object. In that case only StructObject needs to be implemented and the value will provide default implementations for stringification and debug printing.

use minijinja::value::{Value, StructObject};

struct Point(f32, f32, f32);

impl StructObject for Point {
    fn get_field(&self, name: &str) -> Option<Value> {
        match name {
            "x" => Some(Value::from(self.0)),
            "y" => Some(Value::from(self.1)),
            "z" => Some(Value::from(self.2)),
            _ => None,
        }
    }

    fn static_fields(&self) -> Option<&'static [&'static str]> {
        Some(&["x", "y", "z"][..])
    }
}

let value = Value::from_struct_object(Point(1.0, 2.5, 3.0));

Full Example

The following example shows how to implement a dynamic object which represents a struct. Note that in this case not only Object needs to be implemented, but also Debug and Display no longer come for free.

use std::fmt;
use minijinja::value::{Value, Object, ObjectKind, StructObject};

#[derive(Debug, Clone)]
struct Point(f32, f32, f32);

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {}, {})", self.0, self.1, self.2)
    }
}

impl Object for Point {
    fn kind(&self) -> ObjectKind<'_> {
        ObjectKind::Struct(self)
    }
}

impl StructObject for Point {
    fn get_field(&self, name: &str) -> Option<Value> {
        match name {
            "x" => Some(Value::from(self.0)),
            "y" => Some(Value::from(self.1)),
            "z" => Some(Value::from(self.2)),
            _ => None,
        }
    }

    fn static_fields(&self) -> Option<&'static [&'static str]> {
        Some(&["x", "y", "z"][..])
    }
}

let value = Value::from_object(Point(1.0, 2.5, 3.0));

Struct As context

Structs can also be used as template rendering context. This has a lot of benefits as it means that the serialization overhead can be largely to completely avoided. This means that even if templates take hundreds of values, MiniJinja does not spend time eagerly converting them into values.

Here is a very basic example of how a template can be rendered with a dynamic context. Note that the implementation of fields is optional for this to work. It’s in fact not used by the engine during rendering but it is necessary for the debug() function to be able to show which values exist in the context.

use minijinja::value::{Value, StructObject};

pub struct DynamicContext {
    magic: i32,
}

impl StructObject for DynamicContext {
    fn get_field(&self, field: &str) -> Option<Value> {
        match field {
            "pid" => Some(Value::from(std::process::id())),
            "env" => Some(Value::from_iter(std::env::vars())),
            "magic" => Some(Value::from(self.magic)),
            _ => None,
        }
    }
}

let tmpl = env.template_from_str("HOME={{ env.HOME }}; PID={{ pid }}; MAGIC={{ magic }}")?;
let ctx = Value::from_struct_object(DynamicContext { magic: 42 });
let rv = tmpl.render(ctx)?;

One thing of note here is that in the above example env would be re-created every time the template needs it. A better implementation would cache the value after it was created first.

Required Methods§

source

fn get_field(&self, name: &str) -> Option<Value>

Invoked by the engine to get a field of a struct.

Where possible it’s a good idea for this to align with the return value of fields but it’s not necessary.

If an field does not exist, None shall be returned.

A note should be made here on side effects: unlike calling objects or calling methods on objects, accessing fields is not supposed to have side effects. Neither does this API get access to the interpreter State nor is there a channel to send out failures as only an option can be returned. If you do plan on doing something in field access that is fallible, instead use a method call.

Provided Methods§

source

fn static_fields(&self) -> Option<&'static [&'static str]>

If possible returns a static vector of field names.

If fields cannot be statically determined, then this must return None and fields should be implemented instead. If however this method is implemented, then fields should not be implemented as the default implementation dispatches to here, or it has to be implemented to match the output.

source

fn fields(&self) -> Vec<Arc<str>>

Returns a vector of field names.

This should be implemented if static_fields cannot be implemented due to lifetime restrictions. The default implementation converts the return value of static_fields into a compatible format automatically.

source

fn field_count(&self) -> usize

Returns the number of fields.

The default implementation uses fields and static_fields automatically.

Implementations on Foreign Types§

source§

impl<'a, T: StructObject + ?Sized> StructObject for &'a T

source§

fn get_field(&self, name: &str) -> Option<Value>

source§

fn static_fields(&self) -> Option<&'static [&'static str]>

source§

fn fields(&self) -> Vec<Arc<str>>

source§

fn field_count(&self) -> usize

source§

impl<T: StructObject> StructObject for Arc<T>

source§

fn get_field(&self, name: &str) -> Option<Value>

source§

fn static_fields(&self) -> Option<&'static [&'static str]>

source§

fn fields(&self) -> Vec<Arc<str>>

source§

fn field_count(&self) -> usize

Implementors§