Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to Call Rust Code From C... Or Any Language

How to Call Rust Code From C... Or Any Language

428167a3ec72235ba971162924492609?s=128

Yehuda Katz

August 02, 2015
Tweet

Transcript

  1. None
  2. Enabler

  3. None
  4. #Disrupt

  5. None
  6. Daemon Collector

  7. None
  8. None
  9. None
  10. C is the Lingua Franca (and it's where the bugs

    in Skylight can still live)
  11. The Rust Compiler Protects Us From Low-Level Bugs

  12. Rust Helps Us Communicate Fundamental Constraints

  13. C Code char*  trace_name(Trace*  trace)  {      /*  impl

     */   }   //  Questions:   //     //  *  Can  trace_name  retain  an  alias  to  the  Trace*?   //  *  Can  trace_name  mutate  the  Trace*?   //  *  Can  a  caller  mutate  the  Trace*  during  trace_name?   //  *  Who  is  responsible  to  freeing  the  char*?
  14. Rust Code fn  trace_name(trace:  &Trace)  -­‐>  String  {    

     /*  impl  */   }   //  Questions:   //     //  *  Can  trace_name  retain  an  alias  to  the  Trace?  No.   //  *  Can  trace_name  mutate  the  Trace?  No.   //  *  Can  a  caller  mutate  the  Trace  during  trace_name?  No.   //  *  Who  is  responsible  to  freeing  the  String?  The  caller.
  15. In scope: Calling Rust from C

  16. Out of scope: Calling C from From Rust

  17. Extern Rust Functions #[no_mangle]   extern  "C"  fn  trace_name(trace:  &Trace)

     -­‐>  String  {
    /*  impl  */
 }
 
 //  Questions:   //     //  *  Can  trace_name  retain  an  alias  to  the  Trace?  No.   //  *  Can  trace_name  mutate  the  Trace?  No.   //  *  Can  a  caller  mutate  the  Trace  during  trace_name?  No.   //  *  Who  is  responsible  to  freeing  the  String?  The  caller.   //  *  TLDR:  The  same  story!
  18. Rust is a DSL for describing ownership concepts that you

    have to think about when writing C or C++ Me, Now
  19. The Concepts of Ownership Are Fundamental to Systems Programming

  20. Rust Gives Us The Vocabulary to Talk About It

  21. Calling Rust From C extern  "C"  fn  trace_id(trace:  &Trace)  -­‐>

     u64; uint64_t  get_trace_id(Trace*  trace)  {      return  trace_id(trace);   }
  22. The Process 1. Identify and implement the shared type definitions

    in Rust and C 2. Use Rust types to flesh out the ownership and mutability rules 3. Decide how to express and "enforce" the requirements from C
  23. Step 1. Identify and implement the shared type definitions in

    Rust and C
  24. Calling Rust From C extern  "C"  fn  trace_id(trace:  &Trace)  -­‐>

     u64;  
  25. Calling Rust From C extern  "C"  fn  trace_id(trace:  &Trace)  -­‐>

     u64;   What is this type in terms of C?
  26. Calling Rust From C extern  "C"  fn  trace_id(trace:  &Trace)  -­‐>

     u64;   What is this type in terms of C? Go-to answer for simple types:
 typedef  u64  uint64_t;
  27. Calling Rust From C extern  "C"  fn  trace_id(trace:  &Trace)  -­‐>

     u64;   uint64_t  trace_id(Trace*  trace); What is this type in terms of C? Go-to answer for simple types:
 typedef  u64  uint64_t;
  28. Calling Rust From C extern  "C"  fn  new_trace()  -­‐>  Box<Trace>;

     
  29. Calling Rust From C extern  "C"  fn  new_trace()  -­‐>  Box<Trace>;

      What is this type in terms of C?
  30. Calling Rust From C extern  "C"  fn  new_trace()  -­‐>  Box<Trace>;

      What is this type in terms of C? Go-to answer for structures:
 typedef  Trace  void  *;
  31. Calling Rust From C extern  "C"  fn  new_trace()  -­‐>  Box<Trace>;

      typedef  void*  Trace;   Trace  new_trace(); What is this type in terms of C? Go-to answer for structures:
 typedef  Trace  void  *;
  32. Simple Example struct  Point  {      x:  i64,  

       y:  i64   }   #[no_mangle]   extern  "C"  fn  new_point(x:  i64,  y:  i64)  -­‐>  Box<Point>  {      Box::new(Point  {  x:  x,  y:  y  })   }   #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   }
  33. Simple Scaffolding From C typedef  void*  Point;   typedef  int64_t

     i64;   typedef  double  f64;   Point  new_point(i64  x,  i64  y);   f64  line_len(Point  p1,  Point  p2);   Point  make_point(i64  x,  i64  y)  {      return  new_point(x,  y);   }   f64  calculate_len(Point  p1,  Point  p2)  {      return  line_len(p1,  p2);   }
  34. Simple Mapping / My Opinion Rust C Portable Copy Types

    u32,  i64,  usize uint32_t,  int64_t,  uintptr_t Your Structures Box<T> typedef  to  void  * Borrowed Structures &T typedef  to  void  * Strings and Vectors String  /  Vec<T>  /  &str  /  &[T] Custom Transport
  35. (In practice you usually end up wrapping the Rust pointer

    in your language's "object")
  36. Step 2.
 Use Rust types to flesh out the ownership

    and mutability rules
  37. #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>

     f64  {      /*  impl  */   }
  38. What we did so far #[no_mangle]   extern  "C"  fn

     line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   } typedef  void*  Point;   typedef  double  f64;   f64  line_len(Point  p1,  Point  p2);
  39. What we did so far #[no_mangle]   extern  "C"  fn

     line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   } typedef  void*  Point;   typedef  double  f64;   f64  line_len(Point  p1,  Point  p2);
  40. What we did so far #[no_mangle]   extern  "C"  fn

     line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   } typedef  void*  Point;   typedef  double  f64;   f64  line_len(Point  p1,  Point  p2);
  41. And now, docs! typedef  void*  Point;   typedef  double  f64;

      /**      line_len  takes  two  Point  structs.  You  can  rely  on  the  fact  that  it        will  not  create  aliases  to  the  Points  that  outlive  the  invocation.      Point  should  be  memory  that  is  valid  and  immutable  for  the  duration      of  the  call  to  the  line_len  function.   */   f64  line_len(const  Point  p1,  const  Point  p2);
  42. Mapping typedef  void*  Poing;   typedef  double  f64;   /**

         line_len  takes  two  Point  structs.  You  can  rely  on  the  fact  that  it        will  not  create  aliases  to  the  Points  that  outlive  the  invocation.      Point  should  be  memory  that  is  valid  and  immutable  for  the  duration      of  the  call  to  the  line_len  function.   */   f64  line_len(const  Point  p1,  const  Point  p2); #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   }
  43. Mapping typedef  void*  Poing;   typedef  double  f64;   /**

         line_len  takes  two  Point  structs.  You  can  rely  on  the  fact  that  it        will  not  create  aliases  to  the  Points  that  outlive  the  invocation.      Point  should  be  memory  that  is  valid  and  immutable  for  the  duration      of  the  call  to  the  line_len  function.   */   f64  line_len(const  Point  p1,  const  Point  p2); #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   }
  44. Mapping typedef  void*  Poing;   typedef  double  f64;   /**

         line_len  takes  two  Point  structs.  You  can  rely  on  the  fact  that  it        will  not  create  aliases  to  the  Points  that  outlive  the  invocation.      Point  should  be  memory  that  is  valid  and  immutable  for  the  duration      of  the  call  to  the  line_len  function.   */   f64  line_len(const  Point  p1,  const  Point  p2); #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   }
  45. Mapping typedef  void*  Poing;   typedef  double  f64;   /**

         line_len  takes  two  Point  structs.  You  can  rely  on  the  fact  that  it        will  not  create  aliases  to  the  Points  that  outlive  the  invocation.      Point  should  be  memory  that  is  valid  and  immutable  for  the  duration      of  the  call  to  the  line_len  function.   */   f64  line_len(const  Point  p1,  const  Point  p2); #[no_mangle]   extern  "C"  fn  line_len(p1:  &Point,  p2:  &Point)  -­‐>  f64  {      /*  impl  */   }
  46. Rust is a DSL for describing ownership concepts that you

    have to think about when writing C or C++
  47. Step 3.
 Decide how to express and "enforce" the requirements

    from C
  48. #include  <ownership.h>;   typedef  void*  Point;   typedef  double  f64;

      int  main()  {      Cell  p1  =  NEW_CELL(new_point(10,  20));      Cell  p2  =  NEW_CELL(new_point(30,  10));      printf("%f",  length(p1,  p2));   }   double  length(Cell  c1,  Cell  c2)  {      Point  p1  =  BORROW(c1);      Point  p2  =  BORROW(c2);      return  line_len(p1,  p2);   }
  49. #include  <ownership.h>;   typedef  void*  Point;   typedef  double  f64;

      int  main()  {      Cell  c  =  NEW_CELL(new_point(10,  20));      Point  p1  =  TRANSFER(c);      print_and_dispose(p1);      Point  p2  =  BORROW(c);   }
  50. #include  <ownership.h>;   typedef  void*  Point;   typedef  double  f64;

      int  main()  {      Cell  c  =  NEW_CELL(new_point(10,  20));      Point  p1  =  TRANSFER(c);      print_and_dispose(p1);      Point  p2  =  BORROW(c);   } DYNAMIC  ERROR
  51. Not Covered / My Opinions • Non-Simple return types (out-pointer

    + error boolean) • Non-opaque Rust structures in C (mostly YAGNI) • Dynamic ownership implementation (embedding-specific) • Catching panics (catch_panic coming soon!) • Paranoid extern functions (a different programming pattern)
  52. Thanks! @wycats