写给前端看的Rust教程(12)String 第二部分

1,136 阅读4分钟

原文:24 days from node.js to Rust

前言

到目前为止,你已经掌握了不少知识,开始摩拳擦掌想要写一些API,在你尝试的时候,可能会遇到类似问题:你实现了一个函数,该函数以字符串为参数,代码可能是这样的:

fn my_beautiful_function(arg1: String, arg2: String, arg3: String) {
  /* ... */
}

在调用这个接口的时候你需要这么做:

my_beautiful_function(
  "first".to_owned(),
  "second".to_owned(),
  "third".to_owned()
);

这个接口实现的很糟糕,因为每次调用这个接口都需要调用.to_owned(),可不可以不这么做呢?

正文

&str 与 String

我们需要问自己的是,我们真的需要获取所有权或是借用吗?

借用型

如果你不需要获取数据的所有权,那么将参数设置成引用类型,接下来的问题就变成了:是用&str还是&String?答案通常是&str,让我们看下面这段代码:

fn print_str(msg: &str) {
  println!("{}", msg);
}

你可以直接传递一个字符串字面量,你也可以传递一个&String

fn main() {
  let string_slice = "String slice assigned to variable";
  let real_string = "Genuine String".to_owned();
  print_str(string_slice);
  print_str("Literal slice");
  print_str(&real_string);
}

在任何地方你需要一个&str,那么你也可以直接无障碍的直接使用&String,但反过来就不行了,如果你将函数的定义改成接收&String类型的变量,那么当你传递&str类型参数的时候编译器就会报错

有所有权型

如果你需要一个有所有权的String,那么需要考虑以下几方面:

  1. 手动将&str转化为String类型是件麻烦事,这是你希望实现漂亮API的大障碍
  2. 应该让接口的使用者决定如何创建有所有权的数据,你不要简单地到处接收&str类型的变量然后自己去转换它们

你希望自己的接口能同时处理&strString吗?如果是的话,你需要寻找二者的共同点,下面我们会深入讲解

任意类型

对于一个函数,希望接收一个任意的字符串,我们可以接收一个实现了ToString方法的参数:

se std::str::FromStr; // Imported to use Ipv4Addr::from_str

fn main() {
  let ip_address = std::net::Ipv4Addr::from_str("127.0.0.1").unwrap();
  let string_proper = "String proper".to_owned();
  let string_slice = "string slice";
  needs_string(string_slice);
  needs_string("Literal string");
  needs_string(string_proper);
  needs_string(ip_address);
}

fn needs_string<T: ToString>(almost_string: T) {
  let real_string = almost_string.to_string();
  println!("{}", real_string);
}

这里涉及到了新的语法,即泛型,上面的needs_string函数也可以写成这个样子:

fn needs_string(almost_string: impl ToString) {
  let real_string = almost_string.to_string();
  println!("{}", real_string);
}

那么区别是什么呢?却别就是泛型比impl [trait]更有力量,Rust提供了where关键字可以达到同样的作用:

fn needs_string<T>(string: T)
where
  T: ToString,
{
  println!("{}", string);
}

where语法和<T: ToString>语法没有任何差异

同时接收 &str 或 String

如果你不阅读这部分也不会有太大损失,因为ToString方法覆盖了大部分情况,不过你也确实让API的使用者面临着我们在和 教程6 中用.to_string()&str转换为String同样的问题。很多的结构都实现了Display,而一个用户可以将大量的对象传递给函数,编译器不会给出错误提示。此外,实现.to_string()有时会有代价。用户不一定知道API的实现原理,为此你可能会做很多额外的工作

因为我们已经知道了&String可以代替&str,我们可以将输入泛化为任何可以被引用的东西,如一个Str

相比于Borrow<str>,我们更倾向于AsRef<str>,前者有更多的前提假设,容易失败,后者假设更少,不易失败。此外还有更多的不同,不过这里我们用不到

下面这段代码和上卖弄的很相似,不过ip_address不是一个有效的参数,因为他有一个可打印字符串形式并不意味着可以可以被像&str那样处理

相关阅读

总结

曾经以为Rust中的字符串很简单,这实在是一种单纯的想法,相较于RustJavaScript中的字符串简直要简单太多。不过随着学习的深入,你会逐渐领略到Rust为实现安全性和速度的美妙设计

下一篇文章我们会开启错误处理的学习

更多