迁移到 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()

如果您有自定义的 VarExport 实现覆盖了 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/VVariant,因此我们不得不将 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: 内置类型(GStringVector2Color 等)现在支持 _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:角点构造函数和额外的实用方法。

Rect2Rect2iAabb 上的 from_corners() 构造函数已被弃用,推荐使用 from_position_end(),它更好地描述了参数(位置 + 终点,而不是两个任意角点)。

模块重组

#1531, #1537: metaregister 和 signal 模块已被重组。如果您从这些模块导入特定项,您的导入路径可能需要更新:

符号旧模块 (0.4)新模块 (0.5)
GodotShape, GodotElementShape, Enumeratorgodot::metagodot::meta::shape
ByValue, ByRef, ByObject, ArgPassinggodot::metagodot::meta::conv
ElementTypegodot::metagodot::meta::inspect
GodotConvert (派生宏)godot::registergodot::meta
Export, Var (派生宏)godot::registergodot::register::property
TypedSignal, ConnectHandle, signal 类型godot::registergodot::signal
PropertyHintInfo, PropertyInfo, MethodInfogodot::metagodot::register::info

最常用的符号在 godot::prelude 中仍然可用,不受影响。

Trait 重命名:ArrayElementPackedArrayElement

#1506: trait ArrayElementPackedArrayElement 已分别重命名为 ElementPackedElement

如果您在泛型代码中使用这些 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()?;
}

GStringStringNameNodePath:移除 .arg() 方法

GStringStringName 上的 .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_BINGDRUST_GODOT_BIN
GODOT4_GDEXTENSION_JSONGDRUST_GODOT_API_JSON
GODOT_RUST_NOWARNGDRUST_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)]