Improving Programming Practices for EPICS Collaboration

thoughts on improving programming practices n.w
1 / 16
Embed
Share

Explore the significance of memory safety in programming, the emergence of Rust as a safer alternative to C/C++, and the potential integration of Rust into EPICS to enhance user-facing APIs. Discover the importance of managing C resources efficiently and adopting better coding practices.

  • Programming Practices
  • Rust Language
  • Memory Safety
  • EPICS Collaboration
  • C Resources

Uploaded on | 0 Views


Download Presentation

Please find below an Image/Link to download the presentation.

The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.

You are allowed to download the files provided on this website for personal or commercial use, subject to the condition that they are used lawfully. All files are the property of their respective owners.

The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.

E N D

Presentation Transcript


  1. Thoughts on improving programming practices in EPICS EPICS Collaboration Meeting April 7-11, 2025 Rutherford Appleton Laboratory Jure Varlec Senior Developer jure.varlec@cosylab.com 1

  2. Safety in programming Calls for memory safety are becoming ever louder. Strategies and recommendations of governments follow these trends. Everybody is advised to avoid C and C++ languages. But the world is built upon C and C++! Many languages are considered memory-safe but can't replace C/C++. The Rust language stands out: It can replace C/C++ as a systems programming language. It significantly raises the bar for programming safety. 2

  3. The world is being rewritten in Rust! Well, not quite all of it, but a lot: Numerous hobby projects (re)written wholesale. Bigger projects (web browsers) rewritten piecewise. Ubuntu pondering a switch to uutils/coreutils. Linux kernel incorporating Rust drivers. EPICS is not quite on that train: NickeZ/epics-sys, proof of concept, last commit 7 years ago brunoseivam/epics-base-rust, proof of concept, last commit 6 years ago binp-dev/epics-rs, archived, last commit 6 years ago Araneidae/rust-epics-ca, client only, last commit 5 years ago agerasev/epics-ca, client only, more recent wtup/epics_gen, standalone Excel Db library 3

  4. Introducing Rust into EPICS Should we do it? Yes, I think so! It can't magically solve problems, but it is an opportunity to reimagine user-facing APIs. How do we do it? I don't know! Dealing with an ecosystem that is oriented towards developers, not integrators. This is common to all modern programming languages. This is not the subject if this talk at all ^_^ Can we program in a safer manner without introducing another language? I think so, but it will require a change of mindset. 4

  5. Managing C resources int fd = open("/path/to/file", O_RDONLY); std::ifstream file("/path/to/file"); if (error) { if (error) { puts("Houston, we have a problem "); puts("Houston, we have a problem "); close(fd); return 1; return 1; } } return 0; close(fd); return 0; 5

  6. Scope guard int fd = open("/path/to/file", O_RDONLY); auto fd_guard = make_guard([fd] () { close(fd); }); if (error) { puts("Houston, we have a problem "); close(fd); return 1; } close(fd); Don't just accept lousy patterns encouraged by legacy code! Change your mindset! return 0; 6

  7. Traditional approach to mutual exclusion struct aBigStructure { char* name; char* server; int serverPort; unsigned int inSize; unsigned int outSize; unsigned char* inBuffer; unsigned char* outBuffer; int swapBytes; SOCKET sock; epicsMutexId mutex; epicsMutexId io; epicsTimerId timer; epicsEventId outTrigger; int outputChanged; IOSCANPVT inScanPvt; IOSCANPVT outScanPvt; epicsThreadId sendThread; epicsThreadId recvThread; double recvTimeout; double sendIntervall; }; 7

  8. The traditional approach A better approach 8

  9. Synchronized forbids access without locking struct MyObject { struct Shared { int protField1 = 0; int protField2 = 0; }; MyObject obj; obj.someField = 42; obj.otherField = 3.14; { auto sd = obj.sync.make_guard(); sd->protField1 = 1; sd->protField2 = 0xdeadbeef; int someField; float otherField; Synchronized sync{Shared{}}; } }; The "right way" should be the "easy way" or the "only way"! 9

  10. The user friendliness of aSub The aSub API is very simple complexity is punted to the user! All data is void* unsafe pointer casts. Field types and sizes should be checked at initialization. But who does that!? Dynamic sizing of arrays is confusing: NOA vs. NEA vs. NORD vs. NELM. It's not even obvious that the size is dynamic and how record support handles it! Making an asynchronous subroutine is not trivial. A subroutine returns a long: The return value can cause three different things to happen; long is not appropriate. Most people would use an int, which leads to undefined behavior. 10

  11. Making sure DB and C++ match static constexpr auto mainASub = beginDefinition() record(aSub, "aSubTestRec") { .useInput('D', "name"_c, field(SNAM, "mainASub") Type::String, FieldSize::scalar()) field(INAM, "mainASubInit") .useInput('B', "reading"_c, field(FTA, "USHORT") Type::Float32, FieldSize::scalar()) field(NOA, 13) .useInput('A', "params"_c, field(FTB, "FLOAT") Type::UInt16, FieldSize::precisely(13)) field(FTD, "STRING") .useOutput('E', "numbers"_c, field(FTVE, "LONG") Type::UInt32, FieldSize::atMost(1300)); field(NOVE, 1300) } 11

  12. The subroutine itself The wrapper has a complicated type. A lot is evaluated at compile time! Result myASub(auto rec) { Returns a string view float num = 3.14; if (rec.input("name"_c) == "The Name") { num = rec.input("reading"_c); Completely equivalent to num = *(float*) prec->b; } for (auto p: rec.input("params"_c)) { Returns a std::span num += p; } Pipe syntax, just like shell! using std::ranges::views::transform; rec.output("numbers"_c) = rec.input("params"_c) | tranform(negate); 12

  13. Subroutine itself Result myASub(auto rec) { ... Much more explicit than a simple long return { .processOutputs = true, No need to look up stuff from recGbl.h .severity = epicsSevMinor, .alarm = epicsAlarmSoft, .message = "Param out of bounds", }; This goes both into IOC console and AMSG } 13

  14. Making an asynchronous aSub static constexpr auto mainASub = beginDefinition() .useInput('D', "name"_c, Type::String, FieldSize::scalar()) .useInput('B', "reading"_c, Type::Float32, FieldSize::scalar()) .useInput('A', "params"_c, Type::UInt16, FieldSize::precisely(13)) .useOutput('E', "numbers"_c, Type::UInt32, FieldSize::atMost(1300)) .setExecutionMode(ExecMode::DedicatedThread); 14

  15. What have I learned? Doing things better using C++ is definitely possible but it's really hard: an uphill battle against traditional patterns. The good part: it does not drag in another toolchain or an unstable ecosystem. It makes sense to start with user-facing APIs: subroutines, device support, sequencer. That's where better APIs have the greatest impact. C++ improvements are virtually guaranteed to eventually be usable in EPICS core. But it is possible that Rust will eventually stabilize sufficiently as well. Perhaps it makes sense to try both C++ and Rust wrappers and see what sticks? 15

  16. Thank you. Jure Varlec jure.varlec@cosylab.com www.cosylab.com 16

More Related Content