在Libra中学习Protobuf

Posted 磨链mochain社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Libra中学习Protobuf相关的知识,希望对你有一定的参考价值。

欢迎关注作者主页:http://stevenbai.top

编译安装相关依赖

通过执行./scripts/dev_setup.sh是可以自动安装相关依赖以及编译整个libra系统的. 
如果想自己手工安装protobuf相关依赖可以安装如下步骤:

 
   
   
 
  1. cargo install protobuf

  2. cargo install protobuf-codegen

  • 注意:我当前使用的是v2.6.2

找一个文件试试

这是我从libra中抠出来的,非源文件,位于transaction.proto.

 
   
   
 
  1. syntax = "proto3";


  2. package types;

  3. // Account state as a whole.

  4. // After execution, updates to accounts are passed in this form to storage for

  5. // persistence.

  6. message AccountState {

  7. // Account address

  8. bytes address = 1;

  9. // Account state blob

  10. bytes blob = 2;

  11. }

运行下面的命令:protoc --rust_out . accountstate.proto

可以看到目录下会多出来一个accountstate.rs 
简单看一下生成的AccountState结构体

 
   
   
 
  1. #[derive(PartialEq,Clone,Default)]

  2. pub struct AccountState {

  3. // message fields

  4. pub address: ::std::vec::Vec<u8>,

  5. pub blob: ::std::vec::Vec<u8>,

  6. // special fields

  7. pub unknown_fields: ::protobuf::UnknownFields,

  8. pub cached_size: ::protobuf::CachedSize,

  9. }


  10. impl<'a> ::std::default::Default for &'a AccountState {

  11. fn default() -> &'a AccountState {

  12. <AccountState as ::protobuf::Message>::default_instance()

  13. }

  14. }


  15. impl AccountState {

  16. pub fn new() -> AccountState {

  17. ::std::default::Default::default()

  18. }


  19. // bytes address = 1;



  20. pub fn get_address(&self) -> &[u8] {

  21. &self.address

  22. }

  23. pub fn clear_address(&mut self) {

  24. self.address.clear();

  25. }


  26. // Param is passed by value, moved

  27. pub fn set_address(&mut self, v: ::std::vec::Vec<u8>) {

  28. self.address = v;

  29. }


  30. // Mutable pointer to the field.

  31. // If field is not initialized, it is initialized with default value first.

  32. pub fn mut_address(&mut self) -> &mut ::std::vec::Vec<u8> {

  33. &mut self.address

  34. }


  35. // Take field

  36. pub fn take_address(&mut self) -> ::std::vec::Vec<u8> {

  37. ::std::mem::replace(&mut self.address, ::std::vec::Vec::new())

  38. }


  39. // bytes blob = 2;



  40. pub fn get_blob(&self) -> &[u8] {

  41. &self.blob

  42. }

  43. pub fn clear_blob(&mut self) {

  44. self.blob.clear();

  45. }


  46. // Param is passed by value, moved

  47. pub fn set_blob(&mut self, v: ::std::vec::Vec<u8>) {

  48. self.blob = v;

  49. }


  50. // Mutable pointer to the field.

  51. // If field is not initialized, it is initialized with default value first.

  52. pub fn mut_blob(&mut self) -> &mut ::std::vec::Vec<u8> {

  53. &mut self.blob

  54. }


  55. // Take field

  56. pub fn take_blob(&mut self) -> ::std::vec::Vec<u8> {

  57. ::std::mem::replace(&mut self.blob, ::std::vec::Vec::new())

  58. }

  59. }

除了这些,还为AccountState自动生成了protobuf::Message,protobuf::Clear和std::fmt::Debug接口.

  • 注意如果是Service的话,一样会自动生成一个_grpc.rs文件,用于服务的实现.

利用build.rs自动将proto编译成rs

rust在工程化方面做的非常友好,我们可以编译的过程都可以介入. 
也就是如果我们的项目目录下有build.rs,那么在运行cargo build之前会自动编译然后运行此程序. 相当于在项目目录下运行cargo run build.rs然后再去build. 
这看起来有点类似于go中的//go:generate command argument...,但是要更为强大,更为灵活.

build.rs

在Libra中包含了proto的子项目都会在项目根目录下包含一个build.rs. 其内容非常简单.

 
   
   
 
  1. fn main() {

  2. let proto_root = "src/proto";

  3. let dependent_root = "../../types/src/proto";


  4. build_helpers::build_helpers::compile_proto(

  5. proto_root,

  6. vec![dependent_root],

  7. false, /* generate_client_code */

  8. );

  9. }

这是storage_proto/build.rs, 主要有两个参数是proto_root和dependent_root

1.proto_root表示要自动转换的proto所在目录 
2.dependent_root 表示编译这些proto文件import所引用的目录,也就是protoc -I参数指定的目录. 当然编译成的rs文件如果要正常工作,那么也必须编译dependent_root中的所有proto文件才行 
至于第三个参数generate_client_code, 则表示是否生成client代码,也就是如果proto中包含Service,那么是否也生成grpc client的辅助代码.

简单解读build_helper

build_helper位于common/build_helper,是为了辅助自动将proto文件编译成rs文件.

 
   
   
 
  1. pub fn compile_proto(proto_root: &str, dependent_roots: Vec<&str>, generate_client_code: bool) {

  2. let mut additional_includes = vec![];

  3. for dependent_root in dependent_roots {

  4. // First compile dependent directories

  5. compile_dir(

  6. &dependent_root,

  7. vec![], /* additional_includes */

  8. false, /* generate_client_code */

  9. );

  10. additional_includes.push(Path::new(dependent_root).to_path_buf());

  11. }

  12. // Now compile this directory

  13. compile_dir(&proto_root, additional_includes, generate_client_code);

  14. }


  15. // Compile all of the proto files in proto_root directory and use the additional

  16. // includes when compiling.

  17. pub fn compile_dir(

  18. proto_root: &str,

  19. additional_includes: Vec<PathBuf>,

  20. generate_client_code: bool,

  21. ) {

  22. for entry in WalkDir::new(proto_root) {

  23. let p = entry.unwrap();

  24. if p.file_type().is_dir() {

  25. continue;

  26. }


  27. let path = p.path();

  28. if let Some(ext) = path.extension() {

  29. if ext != "proto" {

  30. continue;

  31. }

  32. println!("cargo:rerun-if-changed={}", path.display());

  33. compile(&path, &additional_includes, generate_client_code);

  34. }

  35. }

  36. }


  37. fn compile(path: &Path, additional_includes: &[PathBuf], generate_client_code: bool) {

  38. ...

  39. }

build.rs直接调用的就是compile_proto这个函数,他非常简单就是先调用compile_dir来编译所有的依赖,然后再编译自身.

而compile_dir则是遍历指定的目录,利用WalkDir查找当前目录下所有的proto文件,然后逐个调用compile进行编译.

rust中的字符串处理

 
   
   
 
  1. fn compile(path: &Path, additional_includes: &[PathBuf], generate_client_code: bool) {

  2. let parent = path.parent().unwrap();

  3. let mut src_path = parent.to_owned().to_path_buf();

  4. src_path.push("src");


  5. let mut includes = Vec::from(additional_includes);

  6. //写成additional_includes.to_owned()也是可以的

  7. let mut includes = additional_includes.to_owned(); //最终都会调用slice的to_vec

  8. includes.push(parent.to_path_buf());

  9. ....

  10. }

要跟操作系统打交道,⾸先需要介绍的是两个字符串类型:OsString 以及它所对应的字符串切⽚类型OsStr。它们存在于std::ffi模块中。

Rust标准的字符串类型是String和str。它们的⼀个重要特点是保证了内 部编码是统⼀的utf-8。但是,当我们和具体的操作系统打交道时,统⼀的 utf-8编码是不够⽤的,某些操作系统并没有规定⼀定是⽤的utf-8编码。所 以,在和操作系统打交道的时候,String/str类型并不是⼀个很好的选择。⽐如在Windows系统上,字符⼀般是⽤16位数字来表⽰的。

为了应付这样的情况,Rust在标准库中又设计了OsString/OsStr来处理 这样的情况。这两种类型携带的⽅法跟String/str⾮常类似,⽤起来⼏乎没 什么区别,它们之间也可以相互转换。

Rust标准库中⽤PathBuf和Path两个类型来处理路径。它们之间的关系 就类似String和str之间的关系:⼀个对内部数据有所有权,还有⼀个只是借 ⽤。实际上,读源码可知,PathBuf⾥⾯存的是⼀个OsString,Path⾥⾯存 的是⼀个OsStr。这两个类型定义在std::path模块中。

通过这种方式可以方便的在字符串和Path,PathBuf之间进行任意转换. 
在compile_dir的第23行中,我们提供给WalkDir::new一个&str,rust自动将其转换为了Path.

FromProto和IntoProto

出于跨平台的考虑,proto文件中的数据类型表达能力肯定不如rust丰富,所以不可避免需要在两者之间进行类型转换. 因此Libra中提供了proto_conv接口专门用于实现两者之间的转换. 
比如:

 
   
   
 
  1. /// Helper to construct and parse [`proto::storage::GetAccountStateWithProofByStateRootRequest`]

  2. ///

  3. /// It does so by implementing `IntoProto` and `FromProto`,

  4. /// providing `into_proto` and `from_proto`.

  5. #[derive(PartialEq, Eq, Clone, FromProto, IntoProto)]

  6. #[ProtoType(crate::proto::storage::GetAccountStateWithProofByStateRootRequest)]

  7. pub struct GetAccountStateWithProofByStateRootRequest {

  8. /// The access path to query with.

  9. pub address: AccountAddress,


  10. /// the state root hash the query is based on.

  11. pub state_root_hash: HashValue,

  12. }

  13. /// Helper to construct and parse [`proto::storage::GetAccountStateWithProofByStateRootResponse`]

  14. ///

  15. /// It does so by implementing `IntoProto` and `FromProto`,

  16. /// providing `into_proto` and `from_proto`.

  17. #[derive(PartialEq, Eq, Clone)]

  18. pub struct GetAccountStateWithProofByStateRootResponse {

  19. /// The account state blob requested.

  20. pub account_state_blob: Option<AccountStateBlob>,


  21. /// The state root hash the query is based on.

  22. pub sparse_merkle_proof: SparseMerkleProof,

  23. }

针对GetAccountStateWithProofByStateRootRequest可以自动在crate::proto::storage::GetAccountStateWithProofByStateRootRequestGetAccountStateWithProofByStateRootRequest之间进行转换,只需要derive(FromProto,IntoProto)即可. 
而针对GetAccountStateWithProofByStateRootResponse 则由于只能手工实现.

 
   
   
 
  1. impl FromProto for GetAccountStateWithProofByStateRootResponse {

  2. type ProtoType = crate::proto::storage::GetAccountStateWithProofByStateRootResponse;


  3. fn from_proto(mut object: Self::ProtoType) -> Result<Self> {

  4. let account_state_blob = if object.has_account_state_blob() {

  5. Some(AccountStateBlob::from_proto(

  6. object.take_account_state_blob(),

  7. )?)

  8. } else {

  9. None

  10. };

  11. Ok(Self {

  12. account_state_blob,

  13. sparse_merkle_proof: SparseMerkleProof::from_proto(object.take_sparse_merkle_proof())?,

  14. })

  15. }

  16. }


  17. impl IntoProto for GetAccountStateWithProofByStateRootResponse {

  18. type ProtoType = crate::proto::storage::GetAccountStateWithProofByStateRootResponse;


  19. fn into_proto(self) -> Self::ProtoType {

  20. let mut object = Self::ProtoType::new();


  21. if let Some(account_state_blob) = self.account_state_blob {

  22. object.set_account_state_blob(account_state_blob.into_proto());

  23. }

  24. object.set_sparse_merkle_proof(self.sparse_merkle_proof.into_proto());

  25. object

  26. }

  27. }


以上是关于在Libra中学习Protobuf的主要内容,如果未能解决你的问题,请参考以下文章

从别人的代码中学习golang系列--03

NeteaseCloudWebApp模仿网易云音乐的vue自己从开源代码中学习到的

CardView 不在披萨片段中显示图片

想在 C# 中学习 ActiveRecord 模式 [关闭]

读程--从未参与的项目中学习

在工作中学习看问题的角度