迁移到 v0.5
本文档涵盖了从 godot-rust 0.4 升级到 0.5 时的弃用和破坏性变更。
为了减少迁移难度,我们建议首先更新到上一个次要版本的最新补丁版本 v0.4.5。 许多变更会提前以弃用警告的形式通知,其中包含了如何切换到新 API 的说明。
您可以通过运行以下命令将 Cargo.toml 更新到最新的补丁版本:
cargo update -p godot
处理完所有弃用警告后,您可以更新到新的次要版本:
cargo upgrade -p godot
目录
Godot 版本
#1484: godot-rust v0.5 增加了 API 级别 4.6,这现在是新的默认级别。
这并不意味着您必须更新您的 Godot 版本——只需提供 Cargo features 来使用较旧的 API 级别,例如 api-4-5。
#1505: API 级别 features 现在仅限于次要版本。如果您之前使用了补丁级别的 features,如 api-4-3-1,您需要将它们更改为相应的次要版本,例如 api-4-3。
Rust 2024 版本
#1500: godot-rust v0.5 使用了 Rust 2024 版本。
最低支持的 Rust 版本(MSRV)现在是 1.94。
引擎 API 中的必需对象
#1383, #1493: GDExtension 4.6 为函数参数和返回值提供了非空("required")类型信息。这意味着许多 Rust API 现在可以更加类型安全,在不应为空值的情况下,您的代码将无需处理 Option。
如果 Godot 保证对象非空,而您使用了 Option<Gd>,则需要将其改为 Gd。
之前 (0.4):
#![allow(unused)] fn main() { let maybe_child: Option<Gd<Node>> = ...; node.add_child(&maybe_child); let tree = node.get_tree().unwrap(); let tween = node.create_tween().unwrap(); }
之后 (0.5):
#![allow(unused)] fn main() { // Option 不再能编译。 let definitely_child: Gd<Node> = ...; // 非空 node.add_child(&definitely_child); let tree = node.get_tree(); // 如果不在树中则 panic;否则使用 get_tree_or_null()。 let tween = node.create_tween(); }
属性 API
Var trait 重新设计
#1454, #1458, #1466, #1469: Var trait 已被重新设计以支持 #[var(pub)],后者会生成基于克隆语义的类型化 Rust getter/setter。trait API 变更如下:
之前 (0.4):
#![allow(unused)] fn main() { pub trait Var: GodotConvert { fn get_property(&self) -> Self::Via; fn set_property(&mut self, value: Self::Via); fn var_hint() -> PropertyHintInfo { ... } } }
之后 (0.5):
#![allow(unused)] fn main() { pub trait Var: GodotConvert { type PubType; fn var_get(field: &Self) -> Self::Via; fn var_set(field: &mut Self, value: Self::Via); fn var_pub_get(field: &Self) -> Self::PubType; fn var_pub_set(field: &mut Self, value: Self::PubType); } }
如果您有手动的 Var 实现,需要更新它们。
然而,对于大多数情况,您可以使用新的 SimpleVar 标记 trait:已经支持 ToGodot + FromGodot + Clone 的类型可以通过 blanket impl 获得自动的 Var 实现。
之前 (0.4):
#![allow(unused)] fn main() { impl Var for MyType { fn get_property(&self) -> Self::Via { self.to_godot_owned() } fn set_property(&mut self, value: Self::Via) { *self = FromGodot::from_godot(value); } } }
之后 (0.5):
#![allow(unused)] fn main() { // MyType: ToGodot + FromGodot + Clone. impl SimpleVar for MyType {} }
#[var] getter/setter 语义
#1454: #[var(get, set)] 键现在彼此独立。
| 属性 | 旧行为 (0.4) | 新行为 (0.5) |
|---|---|---|
#[var] | 生成 getter + setter | 与之前相同 |
#[var(get)] | 禁用 setter | 声明自定义 get_{field},setter 不变 |
#[var(set)] | 禁用 getter | 声明自定义 set_{field},getter 不变 |
#[var(no_get)] | 新增;之前由#[var(set)] 隐含 | 显式禁用 getter |
#[var(no_set)] | 新增;之前由#[var(get)] 隐含 | 显式禁用 setter |
#[var(get = my_fn)] | 自定义 getter 函数 | 与之前相同 |
#[var(set = my_fn)] | 自定义 setter 函数 | 与之前相同 |
之前 (0.4):
#![allow(unused)] fn main() { // 禁用 setter,仅 getter: #[var(get)] my_field: i32, // 禁用 getter,仅 setter: #[var(set)] other_field: f32, }
之后 (0.5):
#![allow(unused)] fn main() { // 禁用 setter,仅 getter: #[var(no_set)] my_field: i32, // 禁用 getter,仅 setter: #[var(no_get)] other_field: f32, // 自定义 getter,生成的 setter: #[var(get)] custom_get_field: i32, }
#[var(pub)] 用于 Rust 侧的访问器
#1458, #1466: #[var] 字段的自动生成 Rust getter/setter 正在逐步淘汰。
在 v0.4 中,#[var] 字段隐式生成了公共的 Rust 访问器方法(例如 get_my_field() / set_my_field())。这些方法现在已被弃用,并将在 v0.6 中移除。
如果您依赖这些生成的 Rust 方法,请使用 #[var(pub)] 显式选择加入:
#![allow(unused)] fn main() { #[var(pub)] my_field: i32, // 生成: fn get_my_field(&self) -> i32 // fn set_my_field(&mut self, value: i32) }
#[var(pub)] 访问器使用基于 Clone 的语义(通过 Var::PubType),而内部面向 Godot 的访问器继续使用 GodotConvert::Via。
自定义 #[var] 访问器的编译时类型检查
#1469: 使用 #[var(get)] 或 #[var(set)] 声明的自定义 getter 和 setter 现在会在编译时进行类型检查。
如果您的 getter 的返回类型或 setter 的参数类型与字段的 Var::PubType 不匹配,您将收到编译错误而不是运行时失败。
#1518: 此外,#[var(no_get)] 与 #[export] 结合现在是一个编译错误。从 GDScript 读取 #[var(no_get)] 字段现在会 panic 并显示有用的错误信息,而不是静默返回默认值。
常量限定的 getter
#1497: 一个启发式方法现在会将前缀为 get_、is_ 或 has_ 的 Godot 方法进行常量限定,将其接收者从 &mut self 更改为 &self。这影响了大量的引擎方法,这些方法之前被不必要地声明为可变。
一个显式的拒绝列表处理了误报情况:实际改变状态的方法仍然保持 &mut self。例如:FileAccess::get_line()(会前进光标)、StreamPeer::get_8() 等。
这是一个技术上的破坏性变更,但使用 mut 绑定的现有代码将继续编译。Clippy 可能会发出关于不必要的 mut 的警告。
之前 (0.4):
#![allow(unused)] fn main() { let mut api: Gd<MultiplayerApi> = ...; let id = api.get_unique_id(); // 需要 &mut self。 }
之后 (0.5):
#![allow(unused)] fn main() { let api: Gd<MultiplayerApi> = ...; let id = api.get_unique_id(); // 现在使用 &self。 }
GodotShape 替换属性 hint API
#1513, #1514, #1534: 类型向 Godot 描述自身的内部表示已被彻底修改。一个新的 GodotShape 枚举(由 GodotConvert::godot_shape() 返回)替换了几个分散的 API。
以下 trait 方法已被移除,取而代之的是统一的 GodotConvert::godot_shape():
Var::var_hint()Export::export_hint()GodotType::property_info()GodotType::property_hint_info()GodotType::godot_type_name()GodotType::class_id()Element::element_type_string()PackedElement::element_type_string()
如果您有自定义的 Var 或 Export 实现覆盖了 var_hint() 或 export_hint(),现在您需要改为实现 GodotConvert::godot_shape(),返回适当的 GodotShape 变体。
这次重构还使得集合中的枚举成为可能。引擎和用户定义的 #[derive(GodotConvert)] 枚举现在都可以用作 Array<T> 或 Dictionary<K, V> 中的元素。
#[func] 边界
#1435: #[func] 中的参数和返回类型已更新。新支持用户定义的枚举。
一些类型也受到了限制——以下类型不再实现 ToGodot/FromGodot,因此不能传递给 #[func] 或从 #[func] 返回:
u64
#1413: 不允许 u64 的主要原因是 GDScript(和 Variant)不能原生存储除 i64 以外的整数,并且很容易引入静默改变值的逻辑错误。另请参阅 API 文档中的理由。
如果您现有使用 u64 的代码,可以使用以下方式:
- 使用
i64并进行转换。 - 如果范围足够,使用任何较小的无符号整数类型。
- 使用自定义类型并覆盖
GodotConvert::godot_shape()并附带参数元数据。
原始指针和 RawPtr<P>
#1436: 引擎 API 中的原始指针(原生结构体、c_void 等)现在被包装在 RawPtr<P> 中,取代了直接的 *const T / *mut T 参数。
构造需要 unsafe,而之前则不需要——您之前可以在从未键入 unsafe 的情况下从 #[func] 返回一个指向局部变量的指针。
之前 (0.4):
#![allow(unused)] fn main() { #[func] fn pass_native_struct(&self, caret_info: *const CaretInfo) -> VarDictionary { let info = unsafe { &*caret_info }; // 直接使用 &CaretInfo。 } #[func] fn native_struct_array_ret(&self) -> *const Glyph { self.some_raw_ptr } }
除了原生结构体类型外,以下用于 ToGodot/FromGodot 的原始指针实现已被移除:
*const std::ffi::c_void*mut std::ffi::c_void*mut *const u8*mut i32*mut f64*mut u8*const u8
之后 (0.5):
#![allow(unused)] fn main() { use godot::meta::conv::RawPtr; #[func] fn pass_native_struct(&self, caret_info: RawPtr<*const CaretInfo>) -> VarDictionary { let caret_raw: *const CaretInfo = caret_info.ptr(); let info = unsafe { &*caret_raw }; // 直接使用 &CaretInfo。 } #[func] fn native_struct_array_ret(&self) -> RawPtr<*const Glyph> { // SAFETY: 调用者必须确保将指针返回给 Godot 是安全的。 unsafe { RawPtr::new(self.some_raw_ptr) } } }
IObject 回调重命名
#1527: IObject trait(以及从 Object 继承的相关 I* traits)中的虚回调现在以 on_ 为前缀,以避免与 Object 上同名方法的命名冲突,并明确这些是回调。
| 旧名称 | 新名称 |
|---|---|
get_property() | on_get() |
set_property() | on_set() |
get_property_list() | on_get_property_list() |
validate_property() | on_validate_property() |
旧名称已被弃用,并将继续运行直到 v0.6。
类型化集合
类型化 Dictionary<K, V>
#1502, #1507, #1516: 字典现在对键和值类型是泛型的:Dictionary<K, V>。这镜像了类型化的 Array<T> API。
Dictionary<K, V>是类型化字典。AnyDictionary是类型擦除的字典(类似于AnyArray)。VarDictionary现在是Dictionary<Variant, Variant>的别名。- 引擎 API 现在接受
&AnyDictionary而不是&VarDictionary,因此您可以直接传递任何&Dictionary<K, V>。 - 类型化字典迭代器可用:
dict.iter_shared()返回(K, V)对。
这在很大程度上是一个向后兼容的变更,因为 VarDictionary 仍然存在,但仍有一些地方需要注意。
接受值的方法已将边界从 ToGodot 更改为 AsArg<Variant>,作为 AsArg<K>/AsArg<V> 的特例。其 ToGodot::Pass 不是 ByValue 的元素(例如对象、可调用对象)现在需要按引用传递:
之前 (0.4):
#![allow(unused)] fn main() { dict.set("hello", gd); }
之后 (0.5):
#![allow(unused)] fn main() { dict.set("hello", &gd); }
vdict! 宏使用 => 分隔符
#1523: vdict! 宏现在使用 => 而不是 : 来分隔键和值。这避免了与 Rust 的 token tree 解析的歧义,并且无需将复杂的键表达式包裹在括号中。这也更符合像 maplit 这样的 crate。
之前 (0.4):
#![allow(unused)] fn main() { let tiles = vdict! { (Vector2i::new(1, 2)): Tile::GRASS, (Vector2i::new(1, 3)): Tile::WATER, }; }
之后 (0.5):
#![allow(unused)] fn main() { let tiles = vdict! { Vector2i::new(1, 2) => Tile::GRASS, Vector2i::new(1, 3) => Tile::WATER, }; }
秉承 godot-rust 的传统,在过渡期内 : 语法继续受支持(带有弃用警告)。
iarray! 和 idict! 宏用于类型推断
#1530: godot-rust v0.5 经历了一次重大的重构,以在 Dictionary<K, V> 中维持良好的 Variant 支持。
简而言之,我们不能再依赖 K/V 是 Variant,因此我们不得不将 ToGodot 替换为更通用的 AsArg<K/V> 边界。这带来了一个代价:由于 AsArg 转换的候选更多,在无法从上下文(如函数返回类型)推导类型的情况下,类型推断会产生歧义。
#![allow(unused)] fn main() { let d = dict! { "key": 10 }; // 错误: "key" 可能是 GString、StringName 或 Variant。 // dict! 现在需要显式类型注解: let d: Dictionary<GString, i32> = dict! { "key": 10 }; }
输入三个额外的标识符当然超出了可接受的范围,所以我们有一个解决方案。
新的宏 iarray! 和 idict!("inferred")可以在没有显式类型提示的情况下创建数组和字典。
它们使用有观点但无歧义的类型推断。例如,iarray!["hello"] 推断为 Array<GString>,永远不会是 Array<StringName>。目前并非所有元素类型都受支持。
现有的 array! 和 dict! 宏保持不变,仍然需要类型上下文(注解、函数参数等)。
#![allow(unused)] fn main() { // 不需要类型注解: let ints = iarray![3, 1, 4]; // Array<i32> let strs = iarray!["a", "b"]; // Array<GString> let d = idict! { "key" => 10 }; // Dictionary<GString, i32> // array!/dict! 在类型上下文下仍然有效: let ints: Array<i64> = array![3, 1, 4]; }
AnyArray -- 协变的类型化/非类型化数组
#1422, #1434: 新的 AnyArray 类型提供了一个类型擦除的数组,可以存储类型化 Array<T> 或非类型化 VarArray。它是协变的:任何 &Array<T> 都可以在需要 &AnyArray 的地方使用,得益于 Deref 强制转换。
#![allow(unused)] fn main() { use godot::builtin::{Array, AnyArray, VarArray}; let typed: Array<i64> = array![1, 2, 3]; // Deref 强制转换:在需要 AnyArray 的地方使用类型化数组。 let any_ref: &AnyArray = &typed; assert_eq!(any_ref.len(), 3); // 对拥有的值进行显式向上转换。 let any: AnyArray = typed.clone().upcast_any_array(); // 向下转换回类型化数组。 let back: Array<i64> = any.try_cast_array::<i64>() .expect("转换回类型化数组"); }
之前接受 &VarArray 参数的引擎 API(如 Callable::callv())现在接受 &AnyArray,这意味着您可以直接传递任何 &Array<T> 而无需手动转换。
如果您之前显式构造 VarArray 以传递给此类方法,请注意 &VarArray 通过 Deref 强制转换为 &AnyArray,因此在大多数情况下应该不需要更改。
对于拥有的值(例如由虚函数返回的),您可以使用 upcast_any_array() 方法将 Array<T> 转换为 AnyArray。
核心 API 变更
内置方法的 Builder API
#1424: 内置类型(GString、Vector2、Color 等)现在支持 _ex() builder 模式来处理带默认参数的方法,这与引擎类方法已有的工作方式一致。
之前 (0.4):
#![allow(unused)] fn main() { let pi = GString::num(std::f64::consts::PI, 3); }
之后 (0.5):
#![allow(unused)] fn main() { let pi = GString::num_ex(std::f64::consts::PI).decimals(3).done(); }
非 _ex 变体仍然可用,并使用 Godot 的默认参数值。
在 v0.4 中,我们经常模拟 builder 模式——并且不一致:
- 通过要求所有参数
- 通过多个方法
- 通过我们手工制作的 builder
如果您使用了这些方法,您的代码需要更新为新的模式。
类型安全的 duplicate_node() 和 duplicate_resource()
#![allow(unused)] fn main() { let node: Gd<Node2D> = Node2D::new_alloc(); let copy: Gd<Node2D> = node.duplicate_node(); // 直接类型化。 // 用于细粒度控制的 Builder 模式: let copy = node.duplicate_node_ex() .flags(DuplicateFlags::SIGNALS | DuplicateFlags::GROUPS) .done(); let resource: Gd<Resource> = Resource::new_gd(); let copy: Gd<Resource> = resource.duplicate_resource(); // 深度复制 (Godot 4.5+): let deep = resource.duplicate_resource_ex() .deep(DeepDuplicateMode::ALL) .done(); }
旧的 Node::duplicate() 和 Resource::duplicate() 方法已被弃用,推荐使用这些新的类型安全变体。
几何 API 变更
#1447, #1461, #1463: 几种几何类型增加了新方法:
Aabb::get_endpoint()-- 返回包围盒的一个角顶点。Aabb清理:额外的方法如get_center()和get_support()(之前是support())。Rect2/Rect2i:角点构造函数和额外的实用方法。
Rect2、Rect2i 和 Aabb 上的 from_corners() 构造函数已被弃用,推荐使用 from_position_end(),它更好地描述了参数(位置 + 终点,而不是两个任意角点)。
模块重组
#1531, #1537: meta、register 和 signal 模块已被重组。如果您从这些模块导入特定项,您的导入路径可能需要更新:
| 符号 | 旧模块 (0.4) | 新模块 (0.5) |
|---|---|---|
GodotShape, GodotElementShape, Enumerator | godot::meta | godot::meta::shape |
ByValue, ByRef, ByObject, ArgPassing | godot::meta | godot::meta::conv |
ElementType | godot::meta | godot::meta::inspect |
GodotConvert (派生宏) | godot::register | godot::meta |
Export, Var (派生宏) | godot::register | godot::register::property |
TypedSignal, ConnectHandle, signal 类型 | godot::register | godot::signal |
PropertyHintInfo, PropertyInfo, MethodInfo | godot::meta | godot::register::info |
最常用的符号在 godot::prelude 中仍然可用,不受影响。
Trait 重命名:ArrayElement 和 PackedArrayElement
#1506: trait ArrayElement 和 PackedArrayElement 已分别重命名为 Element 和 PackedElement。
如果您在泛型代码中使用这些 trait 作为边界,请相应地更新名称。
其他变更
GFile::read_as_gstring_entire() 参数移除
#1494: skip_cr 参数已从 GFile::read_as_gstring_entire() 中移除,以在不同 Godot 版本之间提供统一的 API。
之前 (0.4):
#![allow(unused)] fn main() { let text = gfile.read_as_gstring_entire(true)?; }
之后 (0.5):
#![allow(unused)] fn main() { let text = gfile.read_as_gstring_entire()?; }
GString、StringName 和 NodePath:移除 .arg() 方法
GString 和 StringName 上的 .arg() 方法已被移除。它们曾用于在将参数传递给期望不同字符串类型的函数时在 Godot 字符串类型之间进行转换。其思路是提出一个更通用的 AsArg 转换方法,将来可能会保留 .arg() 名称用于此目的。
要迁移,请使用 T::from():
之前 (0.4):
#![allow(unused)] fn main() { // Node::is_in_group() 接受 impl AsArg<StringName> let group: GString = some_group_name(); node.is_in_group(group.arg()); }
之后 (0.5):
#![allow(unused)] fn main() { let group: GString = some_group_name(); node.is_in_group(&StringName::from(&group)); }
环境变量重命名
#1521: 所有环境变量现在都使用统一的 GDRUST_ 前缀。旧的名称仍然被识别,但会发出弃用警告。
| 旧名称 | 新名称 |
|---|---|
GODOT4_BIN | GDRUST_GODOT_BIN |
GODOT4_GDEXTENSION_JSON | GDRUST_GODOT_API_JSON |
GODOT_RUST_NOWARN | GDRUST_SUPPRESSED_WARNINGS |
| (v0.5 中新增) | GDRUST_MAIN_EXTENSION |
移除的弃用符号
#1459: 在 v0.4 周期中被弃用的所有符号已被移除。如果您已经在 v0.4.5 中处理了所有弃用警告,那么您应该基本可以了。下表列出了移除的符号及其替代品:
| 移除的符号 | 替代方案 |
|---|---|
VariantArray (类型别名) | VarArray |
ClassName (类型别名) | ClassId |
GodotClass::class_name() | GodotClass::class_id() |
WithBaseField::apply_deferred() | run_deferred() + run_deferred_gd() |
Gd::apply_deferred() | Gd::run_deferred() + Gd::run_deferred_gd() |
Callable::from_local_fn() | Callable::from_fn() |
Callable::from_local_static() | Callable::from_class_static() |
*::hash() (Godot 哈希值) | *::hash_u32() |
Array::bsearch_custom() | Array::functional_ops().bsearch_custom() |
ExtensionLibrary::on_level_init() | ExtensionLibrary::on_stage_init() |
ExtensionLibrary::on_level_deinit() | ExtensionLibrary::on_stage_deinit() |
ExtensionLibrary::on_main_loop_startup() | on_stage_init(InitStage::MainLoop) |
ExtensionLibrary::on_main_loop_shutdown() | on_stage_deinit(InitStage::MainLoop) |
#[class(no_init, base=EditorPlugin)] (警告) | 现在是硬编译错误;使用 #[class(init)] |