Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for no_std #58

Closed
wants to merge 2 commits into from
Closed

Support for no_std #58

wants to merge 2 commits into from

Conversation

lexxvir
Copy link

@lexxvir lexxvir commented May 8, 2019

Hello @nayuki!

Thank you for qrencode crate!

I have added some cfgs that allow this crate to be compiled for no_std (no OS) environment. It would be nice if you merge this PR.

Let me know if you need some changes before merge.

Thanks in advance!

@nayuki
Copy link
Owner

nayuki commented Aug 19, 2019

Interesting proposal to make the library not depend on std. (Sorry for my delayed reply.)

I can see how this could be useful. For example, my C++ library got adapted into the Google Fuchsia OS kernel, and they had to modify the code heavily to fit in that environment.

I am interested in keeping the code clean, minimizing changes, and personally understanding every line of code. Your relatively small changeset (total 23 lines added) is a good start.

I have a preliminary question for you: Can I / should I change my code to use core::i32, core::cmp, etc. so that I can share as much code as possible between have-std and no-std compilations? It's because I want to reduce conditional compilation directives, where bugs can hide when modes are untested.

(A bunch of reference resources for myself from Googling "Rust no_std":

@lexxvir
Copy link
Author

lexxvir commented Aug 21, 2019

Thank you for reply!

I think it is possible to convert you crate to no_std one, that do not require std. One issue I see is impl std::error::Error for DataTooLong because no_std crates have not access Error type. If you agree to remove this impl then I can prepare new PR that converts crate to no_std.

@nayuki
Copy link
Owner

nayuki commented Aug 21, 2019

Interesting. What is a sensible way to return either a QrCode or an error object/message? I tried using Option<QrCode> in the past, but I felt like None is not as descriptive as an error object because in the future there could be more than one reason that the encoding failed.

Also, I noticed that due to my use of Vec, this depends on a memory allocator being present. Will this dependency be a pain point for both the no_std and std environments?

@lexxvir
Copy link
Author

lexxvir commented Aug 22, 2019

You don't require to drop Result as return value of functions. You can just remove impl std::error::Error. You can use core::result:Result instead of std::result:Result.

Removal of impl std::error::Error adds light inconvenience for those who would like to use Box<dyn std::error::Error> for combining errors from different crates. But they can use various error handling crates such as error-chain, quick-error and failure or manually implement conversion from qrcodegen error type to std::error::Error.

Also, from my point of view (just opinion), it will be more idiomatic to replace struct DataTooLong(String) by enum Error with different variants. So one can match for particular error. Textual representation for an error can be implemented as impl core::fmt::Display.

Regarding Vec: in no_std environment Vec and other collections are provided by alloc crate, that works fine with std facade as far as I know. Though, there is no_std applications that deny dynamic memory allocation, so they can't use alloc at all.

As example of no_std crate that have Errors and use collections you can check my crate tlv_parser. It uses failure crate, but it's just implementation details.

@nayuki
Copy link
Owner

nayuki commented Sep 12, 2020

I thought about this topic a couple of times in the past year and have some ideas to share. I mostly agree with what you said regarding members from core, changing Error, etc. What I am most concerned about is the dynamic memory allocator for Vec.

All my "traditional" implementations of this QR Code generator library, including Java, Python, TypeScript, C++, and Rust, use "internal allocation". That means when somebody calls my qrcodegen library code, this library performs a bunch of dynamically sized memory allocations on the heap and frees its own memory before returning. This is not a problem for garbage-collected languages like Java, or for running C++ on a desktop computer with lots of memory.

But in environments like microcontrollers with kilobytes of RAM or operating system kernels where dynamic memory allocation may be expensive or dangerous, I decided it would be best to avoid all heap allocations within my library. Instead, the caller would pass two writable buffers of appropriate size ("external allocation"), my library would allocate O(1) memory on the stack (which does not depend on the length or values of the data in any way), and I would manually manage the temporary scratch space. My library would only use the buffers passed in by the caller, and not allocate/deallocate anything else. This is exactly the approach I took for my C library.

So I feel like the more correct answer is that for my C, C++, and Rust versions, if I could afford the effort to write and continually maintain the code, I would have two flavors - "no-alloc" and "heap-alloc" - in each language.

For reference, my no-alloc C library works like this:

#define MY_MAX_VERSION 7
#define BUF_LEN qrcodegen_BUFFER_LEN_FOR_VERSION(MY_MAX_VERSION)
uint8_t dataAndTemp[BUF_LEN] = {'H','e','l','l','o'};
uint8_t qr[BUF_LEN];
bool ok = qrcodegen_encodeBinary(dataAndTemp, 5, qr, qrcodegen_Ecc_HIGH,
    qrcodegen_VERSION_MIN, MY_MAX_VERSION, qrcodegen_Mask_AUTO, false);

print qrcodegen_getModule(qr, x, y) ...

I think you can imagine how it translates to Rust. As a bonus for Rust, I can return a QrCode object that captures a reference of a temporary [u8] slice, which will lock that slice from writing/disposal until the QrCode object is destroyed:

// Note: qrcodegen_BUFFER_LEN_MAX = 3918;
let mut dataAndTemp = [0u8; 3918];
let mut qrBuffer = [0u8; 3918];
dataAndTemp[0] = 'H' as u8;
dataAndTemp[1] = 'e' as u8;
dataAndTemp[2] = 'l' as u8;
dataAndTemp[3] = 'l' as u8;
dataAndTemp[4] = 'o' as u8;
let qr: Option<QrCode> = QrCode::encode_binary(&mut dataAndTemp, 5, &mut qrBuffer, QrCodeEcc::HIGH,
    QrCode::VERSION_MIN, QrCode::VERSION_MAX, QrCodeMask::AUTO, false);
// qr must retain a reference to qrBuffer, which will constrain its mutability and lifetime

print qr.get_module(x, y) ...

@nayuki nayuki closed this Sep 21, 2020
@nayuki nayuki mentioned this pull request Sep 13, 2021
@nayuki
Copy link
Owner

nayuki commented Nov 15, 2021

Today's initial commit of a Rust no-heap no_std implementation: https://github.com/nayuki/QR-Code-generator/tree/d8ea85f2e2a62852217b71151029cee7cc019139

@mcroad
Copy link

mcroad commented Dec 25, 2021

@nayuki Any idea when it will be published?

@nayuki
Copy link
Owner

nayuki commented Dec 25, 2021

@mcroad: I consider my rust-no-heap implementation a fringe one. It will not be published on crates.io. You will have to manually download the source from Git.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants