在看了许多安利 rust 的视频之后, 我决定花一个月的时间来尝试一下 rust. 一周过去了, 我对 Ownership, Lifetime 这些概念还是一头雾水, 但我也不求立刻理解 rust 里所有的概念, 因为它的其它部分同样引人注目.
学习一门新语言, 比较好的方法就是和你已经掌握的语言作对比. “The Rust Programming Language” 中有一个很好的例子. 让我们通过它来对比一下 elixir 和 rust 的异同.
我们知道在 linux 系统中有一个很好用的搜索工具: grep. 这个例子就是模仿 grep 编写一个命令行程序, 对一个 txt 文件进行全文搜索, 返回匹配到的所有行.
新建项目
rust
cargo new --bin greprs
elixir
mix new grepex
两种语言都提供了专门的构建工具, 非常方便.
从命令行读取参数
rust
src/main.rs
use std::env;
use std::process;
use greprs::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {}", err);
process::exit(1);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
if let Err(e) = greprs::run(config) {
println!("Application error: {}", e);
process::exit(1);
}
}
src/lib.rs
pub struct Config {
pub query: String,
pub filename: String,
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config {
query: query,
filename: filename,
})
}
}
// ...
elixir
grepex.exs
import Grepex
args = System.argv()
config = new(Config, args) |> unwrap_or_else(fn err ->
IO.puts "Problem parsing arguments: #{err}"
System.stop 1
end)
IO.puts "Searching for #{config.query}"
IO.puts "In file #{config.filename}"
case Grepex.run(config) do
{:error, e} ->
IO.puts "Application error: #{e}"
System.stop 1
_ -> nil
end
lib/grepex.ex
defmodule Config do
defstruct [:query, :filename]
end
defmodule Grepex do
@moduledoc """
Documentation for Grepex.
"""
import Config
def unwrap_or_else({:ok, result}, _), do: result
def unwrap_or_else({:error, reason}, f), do: f.(reason)
def new(Config, args) when length(args) < 2 do
{:error, "not enough arguments"}
end
def new(Config, args) do
[query, filename|_] = args
{:ok, %Config{
query: query,
filename: filename,
}}
end
# ...
rust 中的
use
类似于 elixir 的alias
. 两种语言中都有struct
, 然而 elixir 作为动态类型语言, 它的 struct 是通过 map 来实现的. 而rust 中, struct 是一种集合类型.unwrap
是 rust 中很常见的一个错误处理的机制, 它的原理很简单, 就是根据参数的类型做出不同的操作. 这里我用 elixir 模拟了一个unwarp_or_else
函数. 顺便说一下, rust 中有enum
的概念, 表示几种类型的统称, 例如Result
就包含了Ok
和Err
两种类型, 这是 elixir 里没有的.
打开文件读取数据
rust
src/lib.rs
pub fn run(config: Config) -> Result<(), Box<Error>>{
let mut f = File::open(config.filename)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
for line in search(&config.query, &contents) {
println!("{}", line);
}
Ok(())
}
// ...
elixir
lib/grepex.ex
def run(config) do
with {:ok, f} <- File.open(config.filename),
{:ok, contents} <- read_to_string(f) do
for line <- search(config.query, contents) do
IO.puts line
end
{:ok, nil}
else
error ->
error
end
end
defp read_to_string(file) do
case IO.binread(file, :all) do
{:error, _} = e -> e
data -> {:ok, data}
end
end
# ...
rust 中非常注重错误处理, 为此还提供了
?
作为简写方式. 当函数返回Ok
类型时, 会继续执行下面的代码; 而返回Err
类型时, 就会不执行之后的代码, 直接返回错误值. 而 elixir 中没有 return 的概念, 但也提供了强大的with
语句, 使我们可以在中途停止, 并返回错误值.
测试
src/lib.rs
#[cfg(test)]
mod test {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
}
elixir
test/grepex_test.exs
defmodule GrepexTest do
use ExUnit.Case
doctest Grepex
test "one result" do
query = "cala"
contents = """
Elixir:
scalability, fault-tolerance.
Functional programming.
"""
assert Grepex.search(query, contents) ==
["scalability, fault-tolerance."]
end
end
为了遵循TDD, 所以这里先说下test啦. 一般的顺序是先写好一个返回值为空的函数, 然后为该函数编写测试, 再重复 "运行测试, 实现函数", 直到测试通过. rust 将函数定义与测试代码写在同一个文件中, 这样会更方便查看. elixir 则有专门的
test
文件夹, 便于编写大量的测试. 另外, elixir 的 assert 宏十分优雅.
实现search函数
rust
src/lib.rs
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
elixir
lib/grepex.ex
def search(query, contents) do
contents
|> String.split("\n")
|> do_search(query, [])
end
def do_search([], _, result) do
Enum.reverse result
end
def do_search([line|t], query, result) do
case String.contains? line, query do
true -> do_search(t, query, [line|result])
false -> do_search(t, query, result)
end
end
很典型的过程式与函数式的区别.
小结
得益于强大的类型系统, rust 的 debug 效率非常高. 接下来的时间里, 我想尝试通过
NIF 把 elixir 和 rust 结合在一起.
Rust is fun!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。