Tera 是 Rust 生态中一款高性能模板引擎,灵感源自 Jinja2 与 Django 模板系统。本文从环境搭建、语法入门到实际项目应用,带你零基础掌握 Tera 的使用,涵盖变量插值、条件判断、循环迭代、模板继承、过滤器、自定义函数等核心功能,通过可运行的完整示例快速上手。
一、初识 Tera
Tera 是一个用 Rust 编写的现代模板引擎,它结合了 Jinja2 和 Django 模板的优点,提供了简洁而强大的模板语法。Tera 支持变量、条件语句、循环、宏、过滤器等功能,非常适合构建动态网页、邮件模板、配置文件等场景。
目前 Tera 已获得超过 4,200 个 GitHub Star,版本迭代活跃,是 Rust 生态中广受欢迎的模板引擎选择。
二、环境搭建与第一个模板
2.1 创建新项目
打开终端,执行以下命令创建一个新的 Rust 项目:
ounter(lineounter(linecargo new tera-democd tera-demo
2.2 添加依赖
编辑 Cargo.toml,添加 Tera 依赖:
ounter(lineounter(lineounter(line[dependencies]tera = "1.20"serde = { version = "1.0", features = ["derive"] }
serde 用于数据的序列化,是向模板传递数据的关键依赖。
如果你不需要默认功能(如自动转义、日期处理等),可以按如下方式禁用:
ounter(lineounter(line[dependencies]tera = { version = "1.20", default-features = false }
2.3 第一个 Hello World 模板
创建模板目录和文件:
ounter(linemkdir templates
在 templates 目录下创建 hello.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line<!DOCTYPE html><html><head><title>{{ title }}</title></head><body><h1>Hello, {{ name }}!</h1><p>当前时间:{{ now() | date(format="%Y-%m-%d %H:%M:%S") }}</p></body></html>
在 src/main.rs 中编写渲染代码:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse tera::{Tera, Context};fn main() {// 加载 templates 目录下所有 .html 模板文件let tera = match Tera::new("templates/**/*.html") {Ok(t) => t,Err(e) => {println!("模板解析错误: {}", e);std::process::exit(1);}};// 创建上下文并插入数据let mut context = Context::new();context.insert("title", &"Tera 入门教程");context.insert("name", &"Rust 开发者");// 渲染模板let rendered = tera.render("hello.html", &context).unwrap();println!("{}", rendered);}
运行项目:
ounter(linecargo run
你将在控制台看到生成的完整 HTML 内容。
三、模板语法详解
3.1 变量插值
Tera 使用 {{ variable }} 语法进行变量插值,支持嵌套属性访问:
ounter(lineounter(line<p>{{ user.name }}</p><p>{{ user.address.city }}</p>
3.2 条件判断
使用 {% if %} 语句进行条件控制:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{% if user.is_logged_in %}<p>欢迎回来,{{ user.name }}!</p>{% elif user.is_guest %}<p>欢迎访客!</p>{% else %}<p>请先登录。</p>{% endif %}
Tera 还提供了丰富的内置测试器(tester),可以在条件中使用:
defined:检查变量是否已定义odd/even:检查数字是否为奇数/偶数string/number:检查值的类型iterable:检查值是否可迭代
ounter(lineounter(lineounter(line{% if user.age is even %}<p>{{ user.name }} 的年龄是偶数</p>{% endif %}
3.3 循环迭代
使用 {% for %} 语法遍历数组或可迭代对象:
ounter(lineounter(lineounter(lineounter(lineounter(line<ul>{% for product in products %}<li>{{ product.name }} - ¥{{ product.price }}</li>{% endfor %}</ul>
遍历对象/Map 时可以使用 key-value 形式:
ounter(lineounter(lineounter(line{% for key, value in config %}<p>{{ key }}: {{ value }}</p>{% endfor %}
循环中可用的特殊变量:
loop.index:当前迭代索引(从 1 开始)loop.index0:当前迭代索引(从 0 开始)loop.first:是否为第一次迭代loop.last:是否为最后一次迭代
ounter(lineounter(lineounter(lineounter(lineounter(line{% for product in products %}<div class="item{% if loop.first %}first{% endif %}">第 {{ loop.index }} / {{ products | length }} 件商品:{{ product.name }}</div>{% endfor %}
3.4 包含其他模板
使用 {% include %} 将一个模板的内容嵌入到当前位置:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line<header>{% include "header.html" %}</header><main>{{ content }}</main><footer>{% include "footer.html" %}</footer>
注意:Tera 出于安全考虑,无法直接使用变量拼接路径(如
{% include "partials/" ~ name ~ ".html" %}),必须使用静态字符串。如果需要动态包含,可以使用自定义函数方案实现。
3.5 空白控制
使用 - 符号控制空白的去除:
ounter(lineounter(lineounter(line{%- if show_title -%}<h1>{{ title }}</h1>{%- endif -%}
{%-会去除标签前的空白-%}会去除标签后的空白
3.6 注释
ounter(line{# 这是一条注释,不会出现在生成的 HTML 中 #}
四、模板继承
模板继承是 Tera 的核心特性之一,允许你定义基础布局,并在子模板中填充具体内容。
4.1 创建基础模板
创建 templates/base.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line<!DOCTYPE html><html><head><metacharset="UTF-8"><title>{% block title %}我的网站{% endblock title %}</title><linkrel="stylesheet"href="/css/style.css"></head><body><header><h1>{% block header %}网站标题{% endblock header %}</h1></header><main>{% block content %}<p>默认内容</p>{% endblock content %}</main><footer><p>© 2024 我的网站</p></footer></body></html>
{% block %} 标签定义了可被覆盖的占位区域。
4.2 创建继承的子模板
创建 templates/page.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{% extends "base.html" %}{% block title %}关于我们 - {{ super() }}{% endblock title %}{% block header %}关于我们{% endblock header %}{% block content %}<article><h2>关于这个项目</h2><p>这是一个使用 Tera 模板引擎构建的 Rust 网站。</p></article>{% endblock content %}
{{ super() }} 用于继承父块的内容,将其追加到当前输出中。
4.3 多层继承
Tera 支持多层模板继承,即 A extends B,B extends C。
ounter(lineounter(lineounter(linebase.html└── layout.html└── page.html
每一层都可以覆盖特定的 block,形成灵活的模板体系。
五、完整实战案例
以下我们将创建一个完整的静态网站生成器示例,展示 Tera 在真实项目中的应用。
5.1 项目结构
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linetera-demo/├── Cargo.toml├── src/│ └── main.rs├── templates/│ ├── base.html│ ├── index.html│ ├── post.html│ └── macros.html└── output/
5.2 基础模板
templates/base.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line<!DOCTYPE html><html><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>{% block title %}我的博客{% endblock title %} - Rust 学习笔记</title><style>body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 1rem; }nav { background: #f0f0f0; padding: 1rem; margin-bottom: 2rem; border-radius: 8px; }nav a { margin-right: 1rem; text-decoration: none; color: #0066cc; }footer { margin-top: 2rem; text-align: center; color: #666; border-top: 1px solid #eee; padding-top: 1rem; }article { margin-bottom: 2rem; border-bottom: 1px solid #eee; }h2 { margin-bottom: 0.25rem; }.meta { color: #666; font-size: 0.875rem; margin-bottom: 1rem; }</style></head><body><nav><ahref="/">首页</a><ahref="/posts">文章列表</a><ahref="/about">关于</a></nav>{% block content %}{% endblock content %}<footer><p>© 2024 Rust 学习笔记 | 由 Tera 生成</p></footer></body></html>
5.3 首页模板
templates/index.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{% extends "base.html" %}{% block title %}首页{% endblock title %}{% block content %}<h1>欢迎来到我的博客</h1><p>这里记录了我的 Rust 学习历程和技术笔记。</p><h2>最新文章</h2>{% for post in posts %}{% if loop.index <= 5 %}<article><h2><ahref="/posts/{{ post.slug }}">{{ post.title }}</a></h2><divclass="meta">发布于 {{ post.date | date(format="%Y年%m月%d日") }} | 阅读 {{ post.read_time }} 分钟</div><p>{{ post.summary | truncate(length=150) }}</p></article>{% endif %}{% endfor %}<p><ahref="/posts">查看全部文章 →</a></p>{% endblock content %}
5.4 文章详情模板
templates/post.html:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line{% extends "base.html" %}{% block title %}{{ post.title }}{% endblock title %}{% block content %}<article><h1>{{ post.title }}</h1><divclass="meta">作者:{{ post.author }} | 发布于 {{ post.date | date(format="%Y年%m月%d日") }}</div>{% if post.tags %}<divclass="tags">标签:{% for tag in post.tags %}<ahref="/tags/{{ tag | slugify }}">{{ tag }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</div>{% endif %}<divclass="content">{{ post.content | safe }}</div><hr><p><ahref="/">← 返回首页</a></p></article>{% endblock content %}
5.5 Rust 后端代码
src/main.rs:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse tera::{Tera, Context};use serde::Serialize;use std::fs;use std::path::Path;#[derive(Serialize)]struct Post {title: String,slug: String,author: String,date: String,read_time: u32,summary: String,tags: Vec<String>,content: String,}fn main() -> Result<(), Box<dyn std::error::Error>> {// 加载并编译模板let tera = Tera::new("templates/**/*.html")?;// 创建输出目录let output_dir = Path::new("output");if !output_dir.exists() {fs::create_dir_all(output_dir)?;}// 准备示例数据(真实项目中可从 Markdown 文件读取)let posts = vec![Post {title: "Rust 入门指南".to_string(),slug: "rust-beginner-guide".to_string(),author: "张三".to_string(),date: "2024-01-15".to_string(),read_time: 8,summary: "本文介绍 Rust 语言的基本概念、安装方法和第一个程序...".to_string(),tags: vec!["Rust".to_string(), "入门".to_string()],content: "<p>Rust 是一门系统级编程语言...</p>".to_string(),},Post {title: "Tera 模板引擎完全指南".to_string(),slug: "tera-template-guide".to_string(),author: "张三".to_string(),date: "2024-01-20".to_string(),read_time: 12,summary: "详细讲解 Tera 模板引擎的使用方法和最佳实践...".to_string(),tags: vec!["Rust".to_string(), "Tera".to_string(), "模板".to_string()],content: "<p>Tera 是一款优秀的 Rust 模板引擎...</p>".to_string(),},];// 渲染首页let mut ctx = Context::new();ctx.insert("posts", &posts);let index_html = tera.render("index.html", &ctx)?;fs::write(output_dir.join("index.html"), index_html)?;println!("✅ 已生成: output/index.html");// 渲染每篇文章的详情页for post in &posts {let mut post_ctx = Context::new();post_ctx.insert("post", &post);let post_html = tera.render("post.html", &post_ctx)?;fs::write(output_dir.join(format!("{}.html", post.slug)), post_html)?;println!("✅ 已生成: output/{}.html", post.slug);}println!("✨ 所有模板已渲染完成!");Ok(())}
5.6 运行结果
执行 cargo run 后,output/ 目录下将生成静态 HTML 文件,可以直接部署到任何 Web 服务器上查看。
六、内置过滤器
Tera 提供了丰富的内置过滤器,用于数据的格式化与转换。以下是最常用的过滤器:
safe | {{ html_content \| safe }} | |
escape | {{ user_input \| escape }} | |
lowerupper | {{ name \| lower }} | |
title | {{ "hello world" \| title }} | |
trim | {{ text \| trim }} | |
truncate | {{ long_text \| truncate(length=100) }} | |
date | {{ timestamp \| date(format="%Y-%m-%d") }} | |
slugify | {{ title \| slugify }} | |
urlencode | {{ query \| urlencode }} | |
json_encode | {{ data \| json_encode }} | |
nth | {{ arr \| nth(n=0) }} |
过滤器链式调用
多个过滤器可以串联使用:
ounter(lineounter(line<p>{{ user_comment | escape | truncate(length=200) }}</p><p>{{ post_title | lower | slugify }}</p>
七、自定义过滤器/函数
7.1 注册自定义过滤器
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse tera::{Tera, Result, Value, from_value, to_value};fn uppercase_filter(value: &Value, _args: &HashMap<String, Value>) -> Result<Value> {let s = from_value::<String>(value.clone())?;Ok(to_value(s.to_uppercase()).unwrap())}fn main() {let mut tera = Tera::new("templates/**/*.html").unwrap();tera.register_filter("uppercase", uppercase_filter);// ...}
在模板中使用:
ounter(line<p>{{ "hello world" | uppercase }}</p>
7.2 注册自定义全局函数
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse tera::{Tera, Result, Value, from_value, to_value};use std::collections::HashMap;fn greet(args: &HashMap<String, Value>) -> Result<Value> {let name = match args.get("name") {Some(val) => from_value::<String>(val.clone())?,None => "Guest".to_string(),};Ok(to_value(format!("Hello, {}!", name)).unwrap())}fn main() {let mut tera = Tera::new("templates/**/*.html").unwrap();tera.register_function("greet", greet);// ...}
在模板中使用:
ounter(line<p>{{ greet(name="Alice") }}</p>
八、模板内嵌到二进制文件
在实际生产环境中,将模板文件嵌入到 Rust 二进制文件中可以实现单文件部署,避免运行时文件路径问题。
8.1 添加依赖
ounter(lineounter(lineounter(line[dependencies]tera = "1.20"include_dir = "0.7"
8.2 实现模板内嵌
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineuse include_dir::{include_dir, Dir};use tera::Tera;static TEMPLATE_DIR: Dir = include_dir!("templates");pub fn load_embedded_templates() -> Tera {let mut tera = Tera::default();// 收集所有模板文件let templates: Vec<(&str, &str)> = TEMPLATE_DIR.find("**/*.html").unwrap().filter_map(|entry| {let file = entry.as_file()?;let name = file.path().to_str()?;let content = file.contents_utf8()?;Some((name, content))}).collect();// 一次性添加所有模板(自动处理继承依赖顺序)tera.add_raw_templates(templates).unwrap();tera}
8.3 开发与生产环境切换
通过条件编译,在开发时使用外部文件(便于调试),在生产时使用内嵌模板:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#[cfg(debug_assertions)]pub fn create_tera() -> Tera {Tera::new("templates/**/*").expect("Failed to load templates")}#[cfg(not(debug_assertions))]pub fn create_tera() -> Tera {use include_dir::{include_dir, Dir};static TEMPLATE_DIR: Dir = include_dir!("templates");let mut tera = Tera::default();let templates = TEMPLATE_DIR.find("**/*.html").unwrap().filter_map(|entry| {let file = entry.as_file()?;Some((file.path().to_str()?, file.contents_utf8()?))});tera.add_raw_templates(templates).unwrap();tera}
九、安全最佳实践
自动转义:Tera 默认会对 .html、.htm和.xml文件的内容进行自动转义,防止 XSS 攻击。这遵循 OWASP 推荐的安全标准。
ounter(lineounter(lineounter(lineounter(lineounter(line// 自定义需要转义的文件扩展名tera.autoescape_on(vec![".html", ".html.php"]);// 完全禁用自动转义(仅当确定内容安全时使用)tera.autoescape_on(vec![]);
严格模式:Tera 的严格模式会在变量未定义时报错,有助于捕获模板中的拼写错误。
路径安全:在使用动态模板包含时,永远不要允许用户直接控制模板路径,防止目录遍历攻击。
内容过滤:对用户输入的内容应用适当的过滤器进行转义或清理。
十、常见问题与解决方案
10.1 模板继承顺序问题
当模板之间存在继承关系时,如果子模板先于父模板被加载,会导致解析错误。解决方案是使用 add_raw_templates 一次性添加所有模板,让 Tera 自动处理依赖关系。
10.2 动态模板包含
Tera 不允许使用变量拼接路径进行模板包含。解决方案是通过 Rust 端实现自定义函数来动态获取模板:
ounter(lineounter(lineounter(lineounter(lineounter(linefn get_dynamic_template(args: &HashMap<String, Value>) -> Result<Value> {let template_name = from_value::<String>(args.get("name").unwrap().clone())?;let rendered = TEMPLATES.render(&template_name, &Context::new())?;Ok(to_value(rendered)?)}
在模板中使用 {{ get_dynamic_template(name=var) | safe }}。
10.3 性能优化
使用 lazy_static 或 once_cell 将 Tera 实例声明为全局静态变量,确保模板只编译一次:
ounter(lineounter(lineounter(lineounter(lineounter(lineuse once_cell::sync::Lazy;static TEMPLATES: Lazy<Tera> = Lazy::new(|| {Tera::new("templates/**/*.html").expect("Failed to load templates")});
十一、总结
本文从零开始介绍了 Tera 模板引擎的完整使用路径,涵盖了项目配置、基础语法、模板继承、实战案例以及高级特性。核心要点包括:
使用 Tera::new()加载模板目录,通过Context传递数据{% block %}、{% extends %}、{% include %}实现模块化模板设计丰富的内置过滤器和测试器满足常见格式化需求 支持自定义过滤器和函数扩展模板功能 可将模板嵌入二进制文件实现单文件部署
Tera 的设计范式偏向传统服务端渲染(如 Jinja2),与 React/Vue 等现代前端框架的组件化思路有本质区别。基于这些基础,你可以进一步探索宏系统、自定义测试器等高级特性,构建更灵活的 Web 应用。


无论身在何处
有我不再孤单孤单
长按识别二维码关注我们

夜雨聆风