Integrating Fend - an arbitrary precision aware calculator built in Rust with React Native

Posted in react-native, rust-lang, fend-calculator, native-modules on July 1, 2024 by Hemanta Sapkota ‐ 5 min read

Integrating Fend - an arbitrary precision aware calculator built in Rust with React Native

Exploring Rust applications in React Native opens a world of possibilities for building high-performance, cross-platform mobile applications. In this post, we’ll explore into integrating Fend, an arbitrary precision aware calculator built in Rust, into a React Native project using the react-native-rust library. This example not only showcases a practical utility but also illustrates the seamless integration of Rust with React Native.

You might want to check out my previous guides on Rust Integration and Ray Tracing with Rust for more context on integrating Rust with React Native.

Fend integration demo

Why Rust and React Native?

Rust is celebrated for its performance, safety, and concurrency features. React Native offers a robust framework for developing cross-platform mobile applications using JavaScript and React. By combining Rust and React Native, we can leverage the best of both worlds to create efficient and powerful mobile apps.

Objectives of the Integration

  1. Explore Rust applications in React Native.

  2. Demonstrate a useful utility, such as a calculator, to illustrate the integration process.

Step 1: Write the React Native Component to evaluate & display Fend expressions

import React, {useState} from 'react';
import {ScrollView, StyleSheet, Text, TextInput, View} from 'react-native';
import RustModule from '../../rust_modules/native/bridge/RustModule';
import Markdown from 'react-native-markdown-renderer';
import {MyButton} from '../MyButton';
import {FlashList} from '@shopify/flash-list';

const bgColor = '#ece6df';
const color = '#201a13';
const borderColor = 'black';

export const FendEvaluator = () => {
  const [input, setInput] = useState('');
  const examples = [
    '0x9 + 0x2',
    '6#100 in decimal',
    '36 to base 6',
    '1.5e-6',
    '(2 + 3i) * i',
    '0.(3) to fraction',
    '(1 + 3) * 7',
    '2pi',
    'a = 4 kg; b = 2; a * b^2',
    '0b0011 xor 0b0101',
    '1 << 2',
    '7 >> 1',
    '5\'10" to cm',
    '1 mile to km',
    '1 GiB to bytes',
    '0 °C to °F',
    '100 °C to °F',
    '0 kelvin to °F',
    '100 J/K to J/°C',
    'roll d6',
    '@1970-01-01',
    'sin(1°)',
    'exp 2',
    'avogadro',
    'planck',
    '(\\x.x) 5',
    '(\\x.2x) 5',
    '(x: x to lb to 2 dp) (60 kg)',
    "pi = ' + (pi to string)'",
    "'A' to codepoint",
    '@debug 1+1',
  ];
  const mdStyle = StyleSheet.create({
    text: {
      color,
    },
  });
  const [history, setHistory] = useState<String[]>([]);

  const onEval = async (value: string) => {
    try {
      const result = await RustModule.fendIntegration(value);
      const acc = [...history, `> ${input}\n${result}`];
      setHistory(acc);
    } catch (ex) {
      console.warn(ex);
    }
  };

  const onChangeText = (value: string) => {
    setInput(value);
  };

  return (
    <View
      className={`flex-1 p-6 bg-[${bgColor}] color-[${color}] justify-between`}>
      <FlashList
        keyboardShouldPersistTaps="always"
        keyboardDismissMode="on-drag"
        estimatedItemSize={100}
        ListEmptyComponent={
          <Text className="py-2">Get started from the examples below.</Text>
        }
        ListHeaderComponent={
          <Markdown style={mdStyle}>
            {
              '**fend** is an arbitrary-precision unit-aware calculator built in Rust - [https://printfn.github.io/fend/](https://printfn.github.io/fend/).'
            }
          </Markdown>
        }
        data={history}
        renderItem={({item}) => {
          return (
            <View className="flex flex-row items-center mb-6">
              <Text className="text-base">{item}</Text>
            </View>
          );
        }}
      />
      <View
        className={`py-2 border border-${borderColor} rounded-2xl flex flex-row items-center`}>
        <TextInput
          className="p-4 w-full text-base"
          style={{color}}
          placeholderTextColor={color}
          value={input}
          placeholder="Input any expression"
          onChangeText={onChangeText}
          autoCapitalize="none"
          onSubmitEditing={() => onEval(input)}
        />
      </View>
      <View>
        <ScrollView
          horizontal
          keyboardShouldPersistTaps="always"
          keyboardDismissMode="none">
          {examples.map(item => (
            <MyButton onPress={() => setInput(item)} text={item} />
          ))}
        </ScrollView>
      </View>
    </View>
  );
};

Step 2: Write the Rust integration code for iOS

pub mod fend {
    use fend_core;
    use std::ffi::{CStr, CString};
    use std::os::raw::c_char;
    use std::ptr;
    use std::str;

    #[no_mangle]
    pub extern "C" fn evalute_with_fend(expression: *const c_char) -> *mut c_char {
        if expression.is_null() {
            return create_c_string("Null pointer received");
        }

        let expression_str = match unsafe { CStr::from_ptr(expression).to_str() } {
            Ok(s) => s,
            Err(_) => return create_c_string("Invalid UTF-8 string"),
        };

        let mut context = fend_core::Context::new();
        let result = match fend_core::evaluate(expression_str, &mut context) {
            Ok(res) => res,
            Err(_) => return create_c_string("Evaluation failed"),
        };

        create_c_string(result.get_main_result())
    }

    fn create_c_string(message: &str) -> *mut c_char {
        match CString::new(message) {
            Ok(c_string) => c_string.into_raw(),
            Err(_) => ptr::null_mut(),
        }
    }
}

The Rust code above defines a module fend with a function evalute_with_fend that evaluates mathematical expressions using the Fend library. Here’s a brief breakdown:

Modules and Imports: The code imports necessary modules from the standard library and the fend_core library.

Foreign Function Interface (FFI): The #[no_mangle] attribute and extern “C” declaration make the evalute_with_fend function callable from other programming languages.

Input Handling: The function takes a C string as input (*const c_char) and converts it to a Rust string using CStr::from_ptr.

Context Creation: It creates a new fend_core::Context for evaluating expressions.

Evaluation: The expression is evaluated using fend_core::evaluate.

Output Handling: The result is converted back to a C string using a helper function create_c_string.

Step 3: Write the Rust integration code for Android

pub mod fend_android {
    use jni::objects::{JClass, JString};
    use jni::sys::jstring;
    use jni::JNIEnv;

    #[no_mangle]
    pub unsafe extern "system" fn Java_com_reactnativepro_RustModule_nativeFendIntegration(
        mut env: JNIEnv,
        _class: JClass,
        expression: JString,
    ) -> jstring {
        android_logger::init_once(
            android_logger::Config::default().with_max_level(log::LevelFilter::Trace),
        );
        log::trace!("starting fend");

        let mut context = fend_core::Context::new();

        let expression_str: String = env
            .get_string(&expression)
            .expect("invalid expression string")
            .into();

        let result = fend_core::evaluate(&expression_str, &mut context).unwrap();

        // Convert the Rust String to a Java String
        let output_java_str = env
            .new_string(&result.get_main_result())
            .expect("Couldn't convert Rust String to Java String.");

        // Return the Java String
        **output_java_str
    }
}

The code above integrates the Fend library with Android using JNI (Java Native Interface). Here’s a brief explanation:

Modules and Imports: The code imports necessary modules from the JNI library.

JNI Function Declaration: The function Java_com_reactnativepro_RustModule_nativeFendIntegration is declared with the #[no_mangle] attribute and extern “system” declaration to make it callable from Java.

Logger Initialization: The android_logger::init_once function is used to initialize logging for debugging purposes.

Expression Handling: The Java string (JString) is converted to a Rust string (String) using the JNIEnv::get_string method.

Context Creation: A new fend_core::Context is created for evaluating the expression.

Evaluation: The expression is evaluated using the fend_core::evaluate function.

Result Conversion: The result is converted from a Rust string to a Java string using the JNIEnv::new_string method and returned to the Java caller.

Conclusion

Integrating Rust with React Native provides a powerful way to enhance your mobile applications with performance-critical components. By following this guide, you’ve successfully integrated Fend, an arbitrary precision aware calculator, into a React Native project. This example demonstrates the seamless integration of Rust and React Native and opens the door to building diverse and high-performance applications.

You can also playground with the Fend integration by downloading the ReactNativePro TestFlight and Google Play Beta app from ReactNativePro.com.

comments powered by Disqus