11. 模块和通用代码
了解如何获取公共名称和显式类型参数。
Wave Foundation
分割文件的原因
随着程序的增长,通过将相关函数分组在一起而不是将所有函数放在main.wave中更容易找到它。模块边界决定哪些名称暴露给其他代码。泛型是一种以不同类型重用同一任务的工具,与文件分离无关。
在本章中,我们创建一个两个文件的程序并学习模块别名、选择import以及通用函数和结构。
两个文件程序
在同一目录下创建helpers.wave和main.wave。
helpers.wave 全部:
pub fun double(value: i32) -> i32 {
return value * 2;
}
main.wave 全部:
import("./helpers")::{
double
};
fun main() {
var result: i32 = double(21);
println("{}", result);
}
执行结果:
42
在终端中运行wavec run main.wave。 helpers.wave 也不单独运行。所需源通过import 连接。
helpers中函数前面的pub表示可以被其他模块导入。不需要对外暴露的辅助功能不需要公开。即使您更改了模块的内部实现,也可以通过保留公共函数的契约来减少对所使用代码的更改。
相对路径的基线
./helpers 相对于创建import 句子的源文件的目录。运行程序时,将其与写入文件I/O的工作目录分开。运行时查找文件import的步骤和查找input.txt的步骤是不同的。
对于本地import,可以省略.wave扩展名。如果划分了目录,则根据位置写出路径./module。不要将本地相对路径与获取包依赖项名称的路径混淆。
选择import和别名
选择 import 只会导致在当前文件中直接使用所需的公共名称。如果存在名称冲突或者您想显示该函数属于哪个模块,请使用别名。
import("std::string::len" as strings);
fun main() {
var length: i32 = strings::len("Wave");
println("{}", length);
}
执行结果:
4
strings 是该文件中定义的模块别名。 strings::len 使用模块的名称。用于字段访问的点和用于模块分隔的::是不同的符号。
请勿在一句话中同时使用选项 import 和别名 import。无论您采用哪种风格,请在整个文件中一致地使用它,以便轻松读取名称的来源。
标准库和包
路径std::指向标准库。用户发布API和import所需的模块。并非所有标准库函数都会自动放入当前命名空间中。
外部包路径从包名称开始。包的位置由编译器选项或包管理器提供。首先了解本地模块的边界,然后在Vex 使用方法中学习如何管理依赖关系。
每种类型使用相同的函数
以下函数逐字返回其输入: 对于 i32 和 str,请使用类型参数 T 以避免编写相同的代码两次。
fun identity<T>(value: T) -> T {
return value;
}
fun main() {
var number: i32 = identity<i32>(42);
var text: str = identity<str>("Wave");
println("{} {}", number, text);
}
执行结果:
42 Wave
T是输入类型的地方,不是执行过程中传递的整数值。通过指定类型参数(如 <i32>)来调用它。普通用户泛型函数不会省略类型参数。
identity 不会通过再次分配字符串字节来重复它们。按原样返回值。泛型的语法不会改变数据的复制和所有权规则。
通用机构要求的操作
仅仅因为有类型参数并不意味着所有操作都可以用于所有类型。下面的minimum应该用作可以比较的实际类型。
fun minimum<T>(left: T, right: T) -> T {
if (left < right) {
return left;
}
return right;
}
fun main() {
var small: i32 = minimum<i32>(7, 4);
var wide: i64 = minimum<i64>(100, 20);
println("{} {}", small, wide);
}
执行结果:
4 20
如果更改类型参数,则文本中使用的<和返回必须是相应的类型。读取通用错误时,请检查调用的类型组合和函数体所需的操作。
通用结构
让我们创建 Pair,它绑定两个不同的值。
struct Pair<A, B> {
first: A;
second: B;
}
fun main() {
var item: Pair<i32, str> = Pair<i32, str> {
first: 7,
second: "seven"
};
println("{} {}", item.first, item.second);
}
执行结果:
7 seven
Pair<i32, str> 和 Pair<i64, str> 是不同的具体类型。类型参数的顺序也有意义。阅读声明和生成代码,看看first和second的类型是在哪里确定的。
API姓名及合约公布
当您发布函数时,您不仅指定名称,还指定输入单位、返回值、失败和所有权。例如,调用者将编写的循环取决于read是否读取最大长度或确切长度。
pub是模块之间的公共作用域Wave。这与export(c)不同,它导出外部符号供其他语言调用。您可以在参见FFI查看链接两种语言的完整示例。
练习与完整解答
在 math.wave 上创建一个公共函数 square 并在 main.wave 上将其作为别名调用,以打印 3 和 5 的幂。
math.wave:
pub fun square(value: i32) -> i32 {
return value * value;
}
main.wave:
import("./math" as math);
fun main() {
println("{} {}", math::square(3), math::square(5));
}
执行结果:
9 25
如果错误是功能消失了,请先检查import路径和pub。如果存在名称冲突,请检查调用是否有别名。如果是类型错误,请检查函数的输入和传递的参数的类型。不要试图通过一种路径修正来解决不同的问题。
C 导入功能
extern(c) fun puts(text: ptr<i8>) -> i32;
名称 ABI 后面可以跟有字符串形式的实际交易品种名称。
extern(c, "native_symbol") fun local_name(value: i32) -> i32;
Wave 导出功能
export(c) fun wave_add(left: i32, right: i32) -> i32 {
return left + right;
}
extern 和 export 可用作单个函数和块。导出的函数必须具有特定的签名ABI,因此不能是通用的。
目标条件属性
目标条件属性可以附加到顶级项目。
#[target(os="linux", arch="x86_64")]
extern(c) fun platform_call(value: i32) -> i32;
条件键为 arch、os、env、abi,并且属性适用于下一个顶级项目。
连接你自己写的C函数
本实验适用于使用 C 编译器的本机环境。连接整数函数,无需库分配或字符串处理。
native.c:
#include <stdint.h>
int32_t native_double(int32_t value) { return value * 2; }
main.wave:
extern(c) fun native_double(value: i32) -> i32;
fun main() {
println("{}", native_double(21));
}
从同一工作目录中的终端运行 Linux/macOS。
cc -c native.c -o native.o
wavec build main.wave native.o -o ffi-example
./ffi-example
预期输出为42。在Windows中的MSVC的开发者shell中,使用cl /c native.c /Fonative.obj创建object并连接到wavec build main.wave native.obj -o ffi-example.exe。 object的源架构和目标架构必须相同。这些示例仅使用较小的值。要将大值传递给C函数,还必须单独保证C页的乘法范围。
本地文件路径以./开头。