C or PHP in the rust
My basic starting point is to write some rust code that can be compiled into a library, and write it as a number of C header files, in C for the invoked PHP to do an extension. It's not very simple, but it's interesting.
Rust FFI (Foreign function interface)
The first thing I did was to tinker with the external function interface of the rust and C-connected rust. I have written a flexible library with a simple method (Hello_from_rust) with a single statement (a pointer to a C char, otherwise known as a string), and the following is the "Hello from rust" output after the input.
Hello_from_rust.rs
#![ Crate_type = "Staticlib"]
#![ Feature (LIBC)]
extern crate libc;
Use STD::FFI::CSTR;
#[no_mangle]
pub extern "C" FN hello_from_rust (name: *const libc::c_char) {let
buf_name = unsafe {cstr::from_ptr (name). To_bytes ()};
Let Str_name = String::from_utf8 (Buf_name.to_vec ()). Unwrap ();
Let C_name = format! ("Hello from Rust, {}", str_name);
println! ("{}", c_name);
}
I am from c (or other!) To split the rust library that is called in. Here's a good explanation for what happens next.
Compiling it will get a file of. A, libhello_from_rust.a. This is a static library that contains all of its own dependencies, and we link it when compiling a C program, which allows us to do follow-up things. Note: After we compile we will get the following output:
Note:link against the following native artifacts when linking against it Static library note:the order and any
DUP Lication can is significant on some platforms, and I need to be preserved
note:library:Systemnote:library:pth Read
note:library:c
note:library:m
This is the rust compiler tells us what to link when we don't use this dependency.
Call Rust from C
Now that we have a library, we have to do two things to make sure it is callable from C. First, we need to create a C header file for it, Hello_from_rust.h. Then link to it when we compile.
The following is a header file:
Hello_from_rust.h
#ifndef __hello
#define __hello
void hello_from_rust (const char *name);
#endif
This is a fairly basic header file that provides a signature/definition only for a simple function. Then we need to write a C program and use it.
hello.c
#include <stdio.h>
#include <stdlib.h>
#include "hello_from_rust.h"
int main (int argc, char *argv[]) {
hello_from_rust ("jared!");
}
We compile it by running the code:
Gcc-wall-o Hello_c hello.c-l/USERS/JMCFARLAND/CODE/RUST/PHP-HELLO-RUST-LHELLO_FROM_RUST-LSYSTEM-LPTHREAD-LC-LM
Note that at the end of the-lsystem-lpthread-lc-lm tell GCC not to link those "local antiques", in order to compile our rust library when the rust compiler can provide it.
After running the following code we can get a binary file:
$/hello_c
Hello from rust, jared!
Pretty! We have just called the Rust Library from C. Now we need to understand how the rust library is going into a php extension.
call C from PHP
It took me some time to figure out that this is not the best document in the World for PHP extensions. The best part comes from a PHP source that binds a script Ext_skel (most of which represents an "extended skeleton") that generates most of the boilerplate code you need. You can start with the download, and the unused PHP source, write the code into the PHP directory and run:
$ cd ext/
$/ext_skel--extname=hello_from_rust
This will generate the basic skeleton needed to create the PHP extension. Now, move your folders everywhere you want to keep your extensions locally. and to move your
- . Rust Source
- . Rust Library
- . C Header
into the same directory. So now you should look at a directory like this:
.
├──credits
├──experimental
├──config.m4
├──config.w32
├──hello_from_rust.c
├──hello_from_ Rust.h
├──hello_from_rust.php
├──hello_from_rust.rs
├──libhello_from_rust.a
├──php_hello_ From_rust.h
└──tests
└──001.phpt
One directory, 11 files
You can see a good description of these files on the PHP docs. Create an extended file. We're going to start by editing config.m4.
Do not explain, the following is my results:
Php_arg_with (Hello_from_rust, for hello_from_rust support,
[--with-hello_from_rust Include Hello_from_rust Support])
if test "$PHP _hello_from_rust"!= "no"; then
Php_subst (hello_from_rust_shared_libadd)
Php_add _library_with_path (Hello_from_rust,., Hello_from_rust_shared_libadd)
php_new_extension (Hello_from_rust, HELLO_FROM_RUST.C, $ext _shared)
fi
As I understand it, these are basic macro commands. However, the documentation for these macro commands is rather bad (for example, Google "Php_add_library_with_path" does not appear to have been written by the PHP team). I happen to have this Php_add_library_path macro command in the previous thread that some people are talking about linking a static library in a PHP expansion. Other recommended macro commands in the comments were generated after I ran the Ext_skel.
Now that we have the configuration settings, we need to actually call the library from the PHP script. For this we have to modify the automatically generated files, hello_from_rust.c. First we add the Hello_from_rust.h header file to the include command. Then we want to modify the Confirm_hello_from_rust_compiled definition method.
#include "hello_from_rust.h"
//a bunch of comments and code removed ...
Php_function (confirm_hello_from_rust_compiled)
{
char *arg = NULL;
int Arg_len, Len;
char *STRG;
if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "s", &arg, &arg_len) = = failure) {return
;
}
Hello_from_rust ("Jared (from php!!)!");
Len = spprintf (&STRG, 0, "congratulations! You have successfully modified EXT/%.78S/CONFIG.M4. Module%.78s is now compiled into PHP. "," Hello_from_rust ", Arg);
Return_stringl (STRG, Len, 0);
}
Note: I added Hello_from_rust ("Jared (fromphp!!)!");
Now, we can try to build our extension:
$ phpize $
/configure
$ sudo make install
That's it, generate our meta configuration, run the generated configuration commands, and then install the extension. When I install, I have to use sudo myself, because my users don't have PHP extensions for the installation directory.
Now, we can run it!
$ PHP hello_from_rust.php
functions available in the test extension:
confirm_hello_from_rust_compiled
Hello from Rust, Jared (from php!!)!
congratulations! You have successfully modified EXT/HELLO_FROM_RUST/CONFIG.M4. Module Hello_from_rust is now compiled into PHP.
Segmentation fault:11
Not bad, PHP has entered our C extension, see our list of application methods and call. Then, the C extension has entered our rust library and started printing our strings. That's funny! But...... What happened to the wrong ending?
As I mentioned, here is the use of rust related println! macros, but I didn't do any further debugging on it. If we delete and return a char* substitution from our rust library, the segment error disappears.
Here is the code for rust:
Copy Code code as follows:
#! [Crate_type = "Staticlib"]
#! [Feature (LIBC)]
extern crate libc;
Use Std::ffi::{cstr, CString};
#[no_mangle]
Pub extern "C" FN hello_from_rust (name: *const libc::c_char)-> *const Libc::c_char {
Let Buf_name = unsafe {cstr::from_ptr (name). To_bytes ()};
Let Str_name = String::from_utf8 (Buf_name.to_vec ()). Unwrap ();
Let C_name = format! ("Hello from Rust, {}", str_name);
Cstring::new (C_name). Unwrap (). As_ptr ()
}
and change the C header file:
#ifndef __hello
#define __hello
const char * hello_from_rust (const char *name);
#endif
Also change the C extension file:
Php_function (confirm_hello_from_rust_compiled)
{
char *arg = NULL;
int Arg_len, Len;
char *STRG;
if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "s", &arg, &arg_len) = = failure) {return
;
}
char *str;
str = Hello_from_rust ("Jared (from php!!)!");
printf ("%s\n", str);
Len = spprintf (&STRG, 0, "congratulations! You have successfully modified EXT/%.78S/CONFIG.M4. Module%.78s is now compiled into PHP. "," Hello_from_rust ", Arg);
Return_stringl (STRG, Len, 0);
}
A useless micro-benchmark
So why are you doing this? I really didn't use it in the real world. But I really think the Fibonacci algorithm is a good example of how a PHP expansion is fundamental. Usually straightforward (in Ruby):
def fib (at) does
if (at = = 1 | | at = = 0) return to
else return
fib (at-1) + fib (at-2) end
E nd
And you can improve this bad performance by not using a recursive return:
def fib (at)
does if (at = = 1 | | at = = 0) return to
elsif (val = @cache [at]). Present?
Return Val End total
= 1
parent = 1
gp = 1
(1..at).
each do |i| Total = parent + GP
gp = parent
parent = Total
-
So we're going to write two examples around it, one in PHP, one in rust. See which one is faster. Here is the PHP version:
def fib (at)
does if (at = = 1 | | at = = 0) return to
elsif (val = @cache [at]). Present?
Return Val End total
= 1
parent = 1
gp = 1
(1..at).
each do |i| Total = parent + GP
gp = parent
parent = Total
-
This is the result of its operation:
$ time PHP php_fib.php real
0m2.046s
user 0m1.823s
sys 0m0.207s
Now let's do the rust version. The following are library resources:
Copy Code code as follows:
#! [Crate_type = "Staticlib"]
fn FIB (at:usize)-> usize {
If at = = 0 {
return 0;
else if at = = 1 {
return 1;
}
Let mut total = 1;
Let mut parent = 1;
Let mut gp = 0;
For _ in 1. at {
Total = parent + GP;
GP = parent;
parent = total;
}
return total;
}
#[no_mangle]
Pub extern "C" FN rust_fib (at:usize)-> usize {
FIB (at)
}
Note that I compiled the library rustc-o rust_lib.rs to make the compiler optimize (because we are here standard). Here is the C extension source (related excerpt):
Php_function (confirm_rust_fib_compiled)
{
long;
if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "L", &number) = = failure) {return
;
}
Return_long (RUST_FIB (number));
}
To run a PHP script:
<?php
$br = (php_sapi_name () = "CLI")? "": "<br>";
if (!extension_loaded (' Rust_fib ')) {
dl (' Rust_fib. '). Php_shlib_suffix);
}
for ($i = 0; $i < 100000 $i + +) {
confirm_rust_fib_compiled;
}
? >
This is the result of its operation:
$ time PHP rust_fib.php real
0m0.586s
user 0m0.342s
sys 0m0.221s
You can see it three times times faster than the former! The perfect rust Micro benchmark!